Commit eb8a0d30da

eb8a0d30da0cc150e95d44bad998c13ee2defc25

parent: 1e25fb947d

Unsigned

cmc <hello@cleberg.net> · 2026-07-20 23:16 UTC

feat(a11y): semantic colour system (#21 phase 1)

Replaces every hard-coded colour with semantic asset colours that adapt to
light, dark, and Increase Contrast. Dark mode stays locked, so this is a
pure refactor: the audit reports the same findings before and after.

The accent is now blue rather than cyan, per the tech/DNS theme.

Why custom values rather than the system palette: every system colour
fails WCAG AA in light mode. Measured on white — systemYellow 1.51:1,
systemOrange 2.20:1, systemGreen 2.22:1, systemCyan 2.54:1, systemRed
3.55:1. All of them pass in dark mode, which is why the dark-locked app
looked fine, and why unlocking light mode was never a matter of deleting
.preferredColorScheme(.dark). Every new value clears 4.5:1 as text on its
page, its card, and its own 16% badge tint — the way AppStatusBadgeView
actually draws it.

The accent needed splitting in two. As text on a dark background it must
be light; as a fill behind a white label it must be dark. #4DA3FF reads
8.00:1 as text on black but 2.63:1 behind white text, so StatusInfo /
AccentColor cover the foreground role and AccentFill covers
.borderedProminent. AppOnAccent is the label colour for a solid fill and
flips by scheme.

Colours live in Shared/Colors.xcassets rather than the app catalog: the
Shared folder is already a synchronized group in all three targets, so
the widget and share extension pick the palette up with no project-file
surgery. AccentColor stays in the app catalog as the global tint — and is
now actually defined, having been an empty colorset that silently left
system controls rendering in stock blue while custom chrome used cyan.

Two deliberate visual changes: orange folds into StatusWarning and pink
into StatusCritical. They encoded the same severity as the colours they
now share, and both sites also carry a text label.

Audit findings drop 15 to 14, and one of the originals turned out to be
a phantom: the Inspect contrast failure was the Run button in its
disabled state, which WCAG 1.4.3 exempts. testInspectScreen now types a
domain first so the audit measures an enabled control. Findings also
carry the offending element now, so the remaining clipped-text items name
themselves ("No Portfolio Yet", "Search domains") instead of being
anonymous.

Layout: unified · split

Docs/ACCESSIBILITY.md +58
@@ -6,6 +6,58 @@ large Dynamic Type, element descriptions, trait correctness, and Dynamic Type
66support — the same ground the accessibility pass tracked in
77[issue #21](https://github.com/zerolabsco/domain-dig/issues/21) covers.
88
9## The colour palette
10
11Semantic colours live in `Shared/Colors.xcassets`, which is inside the `Shared`
12file-system-synchronized group and therefore reaches the app, the widget, and
13the share extension automatically. `AccentColor` stays in
14`DomainDig/Assets.xcassets` because it is the system-wide tint resolved via
15`ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME`.
16
17Use the generated asset symbols — `Color(.statusCritical)`, `Color(.appSurface)`
18— never a literal. `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS`
19is on, so these are compile-time checked; a typo will not build.
20
21Every value clears WCAG AA (4.5:1) as text on its page, on its card, **and on
22its own 16% badge tint** — the way `AppStatusBadgeView` actually draws it. The
23worst of those three is shown:
24
25| Role | Light | Dark | Worst light | Worst dark |
26| --- | --- | --- | --- | --- |
27| `StatusInfo` / `AccentColor` | `#0000FF` | `#4DA3FF` | 5.50 | 5.85 |
28| `StatusPositive` | `#146C2E` | `#30D158` | 4.65 | 7.32 |
29| `StatusWarning` | `#7A5600` | `#FFD60A` | 4.74 | 9.61 |
30| `StatusCritical` | `#B3261E` | `#FF6961` | 4.51 | 5.54 |
31| `StatusNeutral` | `#5A5A5F` | `#A1A1A6` | 4.92 | 5.88 |
32
33High Contrast variants push further in the same direction. Surfaces
34(`AppBackground`, `AppSurface`, `AppSurfaceElevated`, `AppSeparator`) carry no
35meaning, so they get Any/Dark and, where useful, High Contrast — but no status
36semantics.
37
38Why custom values instead of the system palette: **every** system colour fails
39in light mode. Measured on white — systemYellow 1.51:1, systemOrange 2.20:1,
40systemGreen 2.22:1, systemCyan 2.54:1, systemRed 3.55:1. All of them pass in
41dark mode, which is why the dark-locked app looked fine and why unlocking light
42mode is impossible without this work.
43
44### The accent has two roles, and they conflict
45
46An accent used as **text on a dark background** must be light. The same accent
47used as a **fill behind a white label** must be dark. One value cannot do both:
48`#4DA3FF` reads at 8.00:1 as text on black, but only 2.63:1 behind white text.
49
50So there are two colours:
51
52- `StatusInfo` / `AccentColor` — the accent as *foreground*: text, icons,
53 bordered-button labels, tab bar.
54- `AccentFill` — the accent as a *filled background* behind a label, used by
55 `.borderedProminent`. Stays dark in both schemes so a white label clears AA
56 (8.59:1 light, 7.56:1 dark).
57
58`AppOnAccent` is the label colour for a solid accent fill and flips by scheme —
59white on the light accent, black on the dark one.
60
961## Findings are reported, not enforced
1062
1163The audit surfaces violations that exist today, so failing on all of them would
@@ -85,6 +137,12 @@ pre-commit that blocks every commit. A hook routinely bypassed with
85137
86138## Notes
87139
140- **Disabled controls are a false positive.** WCAG 1.4.3 exempts inactive
141 components from contrast requirements, but the audit flags them anyway. The
142 Inspect screen's Run button is disabled until a domain is typed, and auditing
143 the empty state reported a contrast failure that was never a real defect —
144 which is why `testInspectScreen` types a domain before auditing. Watch for
145 this before "fixing" a contrast finding on a disabled control.
88146- Audits retry up to three times. Slower machines can miss the audit's internal
89147 deadline (`Audit failed to complete in time`, code `-56`), which is a tooling
90148 timeout, not an app defect. A screen that still cannot be audited is reported
DomainDig/Assets.xcassets/AccentColor.colorset/Contents.json +72 −5
@@ -1,11 +1,78 @@
11{
2 "colors" : [
2 "colors": [
33 {
4 "idiom" : "universal"
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0x00",
9 "green": "0x00",
10 "blue": "0xFF",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x4D",
27 "green": "0xA3",
28 "blue": "0xFF",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x00",
45 "green": "0x00",
46 "blue": "0xCC",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0x7F",
67 "green": "0xBF",
68 "blue": "0xFF",
69 "alpha": "1.000"
70 }
71 }
572 }
673 ],
7 "info" : {
8 "author" : "xcode",
9 "version" : 1
74 "info": {
75 "author": "xcode",
76 "version": 1
1077 }
1178}
DomainDig/BatchResultsView.swift +10 −10
@@ -13,7 +13,7 @@ struct BatchResultsView: View {
1313 if viewModel.batchLookupRunning {
1414 VStack(alignment: .trailing, spacing: 4) {
1515 ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1)))
16 .tint(.cyan)
16 .tint(Color(.statusInfo))
1717 .frame(width: 120)
1818 Text(viewModel.batchProgressLabel)
1919 .font(appDensity.font(.caption2))
@@ -110,7 +110,7 @@ struct BatchResultRowView: View {
110110 if let errorMessage = result.errorMessage {
111111 Text(errorMessage)
112112 .font(appDensity.font(.caption2))
113 .foregroundStyle(result.status == .failed ? .red : .secondary)
113 .foregroundStyle(result.status == .failed ? Color(.statusCritical) : .secondary)
114114 }
115115 }
116116 .frame(maxWidth: .infinity, alignment: .leading)
@@ -138,29 +138,29 @@ struct BatchResultRowView: View {
138138 title: availabilityText,
139139 systemImage: "exclamationmark.circle",
140140 foregroundColor: .secondary,
141 backgroundColor: Color(.systemGray5).opacity(0.55)
141 backgroundColor: Color(.appSurfaceElevated)
142142 )
143143 }
144144
145145 private var quickStatusBadge: AppStatusBadgeModel {
146146 switch result.status {
147147 case .pending:
148 return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
148 return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
149149 case .running:
150 return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16))
150 return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
151151 case .completed:
152152 if result.changeClassification == .critical || result.certificateWarningLevel == .critical || result.riskLevel == .high {
153 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
153 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
154154 }
155155 if result.changeClassification == .warning || result.changeSeverity == .medium || result.certificateWarningLevel == .warning {
156 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
156 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
157157 }
158158 if result.quickStatus == "Changed" {
159 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16))
159 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
160160 }
161 return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
161 return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
162162 case .failed:
163 return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
163 return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
164164 }
165165 }
166166}
DomainDig/ContentView.swift +79 −71
@@ -167,7 +167,7 @@ struct ContentView: View {
167167 }
168168 .background(
169169 LinearGradient(
170 colors: [Color.black, Color(.systemGray6).opacity(0.12)],
170 colors: [Color(.appBackground), Color(.appSurface)],
171171 startPoint: .top,
172172 endPoint: .bottom
173173 )
@@ -302,7 +302,7 @@ struct ContentView: View {
302302 .keyboardType(.URL)
303303 .padding(.horizontal, 12)
304304 .padding(.vertical, appDensity.metrics.controlVerticalPadding)
305 .background(Color(.systemGray6))
305 .background(Color(.appSurfaceElevated))
306306 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
307307 .focused($focusedInputField, equals: .singleDomain)
308308 .onSubmit {
@@ -320,6 +320,7 @@ struct ContentView: View {
320320 .frame(minHeight: appDensity.metrics.controlMinHeight)
321321 }
322322 .buttonStyle(.borderedProminent)
323 .tint(Color(.accentFill))
323324 .disabled(viewModel.trimmedDomain.isEmpty)
324325 } else {
325326 if FeatureAccessService.hasAccess(to: .batchOperations) {
@@ -346,7 +347,7 @@ struct ContentView: View {
346347 .lineLimit(4...10)
347348 .padding(.horizontal, 12)
348349 .padding(.vertical, appDensity.metrics.controlVerticalPadding)
349 .background(Color(.systemGray6))
350 .background(Color(.appSurfaceElevated))
350351 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
351352 .focused($focusedInputField, equals: .bulkDomains)
352353
@@ -360,6 +361,7 @@ struct ContentView: View {
360361 .frame(minHeight: appDensity.metrics.controlMinHeight)
361362 }
362363 .buttonStyle(.borderedProminent)
364 .tint(Color(.accentFill))
363365 .disabled(viewModel.bulkInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.batchLookupRunning)
364366 }
365367 } else {
@@ -549,7 +551,7 @@ struct ContentView: View {
549551 } label: {
550552 Image(systemName: viewModel.isCurrentDomainSaved ? "bookmark.fill" : "bookmark")
551553 .font(appDensity.font(.body, design: .default))
552 .foregroundStyle(viewModel.isCurrentDomainSaved ? .yellow : .secondary)
554 .foregroundStyle(viewModel.isCurrentDomainSaved ? Color(.statusWarning) : .secondary)
553555 }
554556 Menu {
555557 Button("Export TXT") {
@@ -671,7 +673,7 @@ struct ContentView: View {
671673 .frame(maxWidth: .infinity, alignment: .leading)
672674 .padding(.vertical, 8)
673675 .padding(.horizontal, 10)
674 .background(Color(.systemGray6).opacity(0.5))
676 .background(Color(.appSurface))
675677 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
676678 }
677679 }
@@ -696,7 +698,7 @@ struct ContentView: View {
696698 }
697699 .frame(maxWidth: .infinity, alignment: .leading)
698700 .padding(appDensity.metrics.cardPadding)
699 .background(Color(.systemGray6))
701 .background(Color(.appSurfaceElevated))
700702 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
701703 }
702704
@@ -792,7 +794,7 @@ struct SummaryView: View {
792794 .frame(minHeight: appDensity.metrics.rowMinHeight + 12, alignment: .topLeading)
793795 .frame(maxWidth: .infinity, alignment: .leading)
794796 .padding(appDensity.metrics.cardPadding)
795 .background(Color(.systemGray6).opacity(0.5))
797 .background(Color(.appSurface))
796798 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
797799 }
798800 }
@@ -851,22 +853,22 @@ struct RiskSummaryCardView: View {
851853 private var levelColor: Color {
852854 switch report.riskAssessment.level {
853855 case .low:
854 return .green
856 return Color(.statusPositive)
855857 case .medium:
856 return .yellow
858 return Color(.statusWarning)
857859 case .high:
858 return .red
860 return Color(.statusCritical)
859861 }
860862 }
861863
862864 private func factorColor(_ impact: RiskImpact) -> Color {
863865 switch impact {
864866 case .positive:
865 return .green
867 return Color(.statusPositive)
866868 case .neutral:
867869 return .secondary
868870 case .negative:
869 return .red
871 return Color(.statusCritical)
870872 }
871873 }
872874}
@@ -888,7 +890,7 @@ struct InsightsSummaryCardView: View {
888890 HStack(alignment: .top, spacing: 8) {
889891 Image(systemName: "chart.line.uptrend.xyaxis")
890892 .font(appDensity.font(.caption2))
891 .foregroundStyle(.cyan)
893 .foregroundStyle(Color(.statusInfo))
892894 .padding(.top, 2)
893895 Text(insight)
894896 .font(appDensity.font(.caption))
@@ -995,13 +997,13 @@ struct LookupStatusBannerView: View {
995997 private var color: Color {
996998 switch resultSource {
997999 case .live:
998 return .green
1000 return Color(.statusPositive)
9991001 case .cached:
10001002 return .secondary
10011003 case .mixed:
1002 return .yellow
1004 return Color(.statusWarning)
10031005 case .snapshot:
1004 return .orange
1006 return Color(.statusWarning)
10051007 }
10061008 }
10071009
@@ -1029,7 +1031,7 @@ struct DomainChangeSummaryView: View {
10291031 HStack {
10301032 Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle")
10311033 .font(appDensity.font(.caption))
1032 .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .green)
1034 .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : Color(.statusPositive))
10331035 Spacer()
10341036 Text(summary.severity.title.uppercased())
10351037 .font(appDensity.font(.caption2))
@@ -1079,13 +1081,13 @@ struct DomainChangeSummaryView: View {
10791081 if let riskScoreDelta = summary.riskScoreDelta {
10801082 Text("Risk delta: \(riskScoreDelta >= 0 ? "+" : "")\(riskScoreDelta)")
10811083 .font(appDensity.font(.caption2))
1082 .foregroundStyle(riskScoreDelta > 0 ? .orange : .secondary)
1084 .foregroundStyle(riskScoreDelta > 0 ? Color(.statusWarning) : .secondary)
10831085 }
10841086
10851087 if let contextNote = summary.contextNote {
10861088 Text(contextNote)
10871089 .font(appDensity.font(.caption2))
1088 .foregroundStyle(.orange)
1090 .foregroundStyle(Color(.statusWarning))
10891091 }
10901092 }
10911093 .padding(.top, 4)
@@ -1101,9 +1103,9 @@ struct DomainChangeSummaryView: View {
11011103 case .low:
11021104 return .secondary
11031105 case .medium:
1104 return .yellow
1106 return Color(.statusWarning)
11051107 case .high:
1106 return .red
1108 return Color(.statusCritical)
11071109 }
11081110 }
11091111
@@ -1205,7 +1207,7 @@ struct DomainDiffView: View {
12051207 }
12061208 }
12071209 .padding(10)
1208 .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.systemGray6).opacity(0.25))
1210 .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.appSurface))
12091211 .cornerRadius(8)
12101212 }
12111213 } label: {
@@ -1225,7 +1227,7 @@ struct DomainDiffView: View {
12251227 .overlay {
12261228 if highlightedSectionID == section.id {
12271229 RoundedRectangle(cornerRadius: 12)
1228 .stroke(Color.cyan.opacity(0.55), lineWidth: 1)
1230 .stroke(Color(.statusInfo).opacity(0.55), lineWidth: 1)
12291231 }
12301232 }
12311233 }
@@ -1258,9 +1260,9 @@ struct DomainDiffView: View {
12581260 case .low:
12591261 return .blue
12601262 case .medium:
1261 return .yellow
1263 return Color(.statusWarning)
12621264 case .high:
1263 return .red
1265 return Color(.statusCritical)
12641266 }
12651267 }
12661268
@@ -1269,9 +1271,9 @@ struct DomainDiffView: View {
12691271 case .low:
12701272 return .blue
12711273 case .medium:
1272 return .yellow
1274 return Color(.statusWarning)
12731275 case .high:
1274 return .red
1276 return Color(.statusCritical)
12751277 }
12761278 }
12771279
@@ -1337,7 +1339,7 @@ struct DomainSectionView: View {
13371339 CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) {
13381340 if let trackedDomain {
13391341 HStack(spacing: 8) {
1340 AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)))
1342 AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)))
13411343 Button {
13421344 onTogglePinned()
13431345 } label: {
@@ -1638,7 +1640,7 @@ struct IntelligenceSectionView: View {
16381640 if item.isEphemeral {
16391641 Text("Ephemeral")
16401642 .font(appDensity.font(.caption2))
1641 .foregroundStyle(.yellow)
1643 .foregroundStyle(Color(.statusWarning))
16421644 }
16431645 }
16441646 Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x")
@@ -1667,7 +1669,7 @@ struct IntelligenceSectionView: View {
16671669 VStack(alignment: .leading, spacing: 8) {
16681670 Text(title)
16691671 .font(appDensity.font(.subheadline, weight: .semibold))
1670 .foregroundStyle(.cyan)
1672 .foregroundStyle(Color(.statusInfo))
16711673 content()
16721674 }
16731675 }
@@ -1758,7 +1760,7 @@ struct SubdomainsSectionView: View {
17581760 HStack {
17591761 Text("\(group.label).*")
17601762 .font(appDensity.font(.caption))
1761 .foregroundStyle(.cyan)
1763 .foregroundStyle(Color(.statusInfo))
17621764 Spacer()
17631765 Text("\(group.subdomains.count)")
17641766 .font(appDensity.font(.caption2))
@@ -1776,10 +1778,10 @@ struct SubdomainsSectionView: View {
17761778 if row.isInteresting {
17771779 Text("Interesting")
17781780 .font(.system(.caption2, design: .monospaced))
1779 .foregroundStyle(.yellow)
1781 .foregroundStyle(Color(.statusWarning))
17801782 .padding(.horizontal, 8)
17811783 .padding(.vertical, 4)
1782 .background(Color.yellow.opacity(0.14))
1784 .background(Color(.statusWarning).opacity(0.14))
17831785 .clipShape(Capsule())
17841786 }
17851787 }
@@ -1883,7 +1885,7 @@ struct DNSSectionView: View {
18831885 Text(section.title)
18841886 .font(.system(.subheadline, design: .monospaced))
18851887 .fontWeight(.semibold)
1886 .foregroundStyle(.cyan)
1888 .foregroundStyle(Color(.statusInfo))
18871889
18881890 if let message = section.message {
18891891 MessageRowView(text: message.text, isError: message.isError)
@@ -1910,7 +1912,7 @@ struct DNSSectionView: View {
19101912 Text("PTR")
19111913 .font(.system(.subheadline, design: .monospaced))
19121914 .fontWeight(.semibold)
1913 .foregroundStyle(.cyan)
1915 .foregroundStyle(Color(.statusInfo))
19141916 SectionTrustMetadataView(provenance: ptrProvenance, confidence: nil)
19151917 MessageRowView(text: ptrMessage.text, isError: ptrMessage.isError)
19161918 }
@@ -1920,7 +1922,7 @@ struct DNSSectionView: View {
19201922 HStack {
19211923 Text("History")
19221924 .font(appDensity.font(.subheadline, weight: .semibold))
1923 .foregroundStyle(.cyan)
1925 .foregroundStyle(Color(.statusInfo))
19241926 Spacer()
19251927 if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder {
19261928 Button("Load") {
@@ -1990,7 +1992,7 @@ struct WebSectionView: View {
19901992 HStack {
19911993 Text("TLS")
19921994 .font(appDensity.font(.subheadline, weight: .semibold))
1993 .foregroundStyle(.cyan)
1995 .foregroundStyle(Color(.statusInfo))
19941996 Spacer()
19951997 if !sslLoading {
19961998 AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: sslInfo, error: sslError))
@@ -2034,7 +2036,7 @@ struct WebSectionView: View {
20342036 CardView {
20352037 Text("Headers")
20362038 .font(appDensity.font(.subheadline, weight: .semibold))
2037 .foregroundStyle(.cyan)
2039 .foregroundStyle(Color(.statusInfo))
20382040 SectionTrustMetadataView(provenance: httpProvenance, confidence: nil)
20392041 if headersLoading {
20402042 ProgressView("Fetching headers…")
@@ -2052,7 +2054,7 @@ struct WebSectionView: View {
20522054 HStack(alignment: .top, spacing: 4) {
20532055 Text(header.name + ":")
20542056 .font(appDensity.font(.caption))
2055 .foregroundStyle(header.isSecurityHeader ? .yellow : .cyan)
2057 .foregroundStyle(header.isSecurityHeader ? Color(.statusWarning) : Color(.statusInfo))
20562058 Text(header.value)
20572059 .font(appDensity.font(.caption))
20582060 .foregroundStyle(.primary)
@@ -2067,7 +2069,7 @@ struct WebSectionView: View {
20672069 HStack {
20682070 Text("Redirects")
20692071 .font(appDensity.font(.subheadline, weight: .semibold))
2070 .foregroundStyle(.cyan)
2072 .foregroundStyle(Color(.statusInfo))
20712073 Spacer()
20722074 if let finalURL {
20732075 AppCopyButton(value: finalURL, label: "Copy redirect URL")
@@ -2093,7 +2095,7 @@ struct WebSectionView: View {
20932095 .frame(width: 16, alignment: .trailing)
20942096 Text(redirect.statusCode)
20952097 .font(appDensity.font(.caption))
2096 .foregroundStyle(.cyan)
2098 .foregroundStyle(Color(.statusInfo))
20972099 .frame(width: 36, alignment: .leading)
20982100 Text(redirect.url)
20992101 .font(appDensity.font(.caption))
@@ -2163,7 +2165,7 @@ struct EmailSectionView: View {
21632165 HStack(spacing: 8) {
21642166 Text(row.label)
21652167 .font(appDensity.font(.caption))
2166 .foregroundStyle(.cyan)
2168 .foregroundStyle(Color(.statusInfo))
21672169 .frame(width: 76, alignment: .leading)
21682170 AppStatusBadgeView(model: emailRowBadge(row))
21692171 }
@@ -2186,13 +2188,13 @@ struct EmailSectionView: View {
21862188 private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel {
21872189 switch row.statusTone {
21882190 case .success:
2189 return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
2191 return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
21902192 case .warning:
2191 return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
2193 return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
21922194 case .failure:
2193 return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
2195 return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
21942196 case .primary, .secondary:
2195 return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
2197 return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
21962198 }
21972199 }
21982200}
@@ -2227,7 +2229,7 @@ struct NetworkSectionView: View {
22272229 CardView {
22282230 Text("Reachability")
22292231 .font(appDensity.font(.subheadline, weight: .semibold))
2230 .foregroundStyle(.cyan)
2232 .foregroundStyle(Color(.statusInfo))
22312233 SectionTrustMetadataView(provenance: reachabilityProvenance, confidence: nil)
22322234 if reachabilityLoading {
22332235 ProgressView("Checking ports…")
@@ -2252,7 +2254,7 @@ struct NetworkSectionView: View {
22522254 CardView(allowsHorizontalScroll: false) {
22532255 Text("Location")
22542256 .font(appDensity.font(.subheadline, weight: .semibold))
2255 .foregroundStyle(.cyan)
2257 .foregroundStyle(Color(.statusInfo))
22562258 SectionTrustMetadataView(provenance: geolocationProvenance, confidence: geolocationConfidence)
22572259 if geolocationLoading {
22582260 ProgressView("Looking up location…")
@@ -2284,13 +2286,13 @@ struct NetworkSectionView: View {
22842286 CardView(allowsHorizontalScroll: false) {
22852287 Text("Port Scan")
22862288 .font(appDensity.font(.subheadline, weight: .semibold))
2287 .foregroundStyle(.cyan)
2289 .foregroundStyle(Color(.statusInfo))
22882290 SectionTrustMetadataView(provenance: portScanProvenance, confidence: nil)
22892291
22902292 if isCloudflareProxied {
22912293 Text("Domain is behind Cloudflare's proxy. Results reflect the edge, not the origin.")
22922294 .font(appDensity.font(.caption2))
2293 .foregroundStyle(.orange)
2295 .foregroundStyle(Color(.statusWarning))
22942296 .fixedSize(horizontal: false, vertical: true)
22952297 }
22962298
@@ -2314,7 +2316,7 @@ struct NetworkSectionView: View {
23142316 .autocorrectionDisabled()
23152317 .keyboardType(.numberPad)
23162318 .padding(10)
2317 .background(Color(.systemGray6).opacity(0.5))
2319 .background(Color(.appSurface))
23182320 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
23192321
23202322 Button("Scan") {
@@ -2322,6 +2324,7 @@ struct NetworkSectionView: View {
23222324 onScanCustomPorts()
23232325 }
23242326 .buttonStyle(.borderedProminent)
2327 .tint(Color(.accentFill))
23252328 .disabled(customPortScanLoading)
23262329
23272330 if customPortScanLoading {
@@ -2344,13 +2347,13 @@ struct NetworkSectionView: View {
23442347 private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel {
23452348 switch row.statusTone {
23462349 case .success:
2347 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
2350 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
23482351 case .warning:
2349 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
2352 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
23502353 case .failure:
2351 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
2354 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
23522355 case .primary, .secondary:
2353 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
2356 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
23542357 }
23552358 }
23562359}
@@ -2395,13 +2398,13 @@ struct PortRowsView: View {
23952398 private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel {
23962399 switch row.statusTone {
23972400 case .success:
2398 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
2401 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
23992402 case .warning:
2400 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
2403 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
24012404 case .failure:
2402 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
2405 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
24032406 case .primary, .secondary:
2404 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
2407 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
24052408 }
24062409 }
24072410}
@@ -2413,7 +2416,7 @@ struct SectionTitleView: View {
24132416 var body: some View {
24142417 Text(title)
24152418 .font(appDensity.font(.headline, design: .default, weight: .semibold))
2416 .foregroundStyle(.white)
2419 .foregroundStyle(.primary)
24172420 }
24182421}
24192422
@@ -2442,7 +2445,7 @@ struct CardView<Content: View>: View {
24422445 }
24432446 .frame(maxWidth: .infinity, alignment: .leading)
24442447 .padding(appDensity.metrics.cardPadding)
2445 .background(Color(.systemGray6).opacity(0.5))
2448 .background(Color(.appSurface))
24462449 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
24472450 }
24482451
@@ -2484,7 +2487,7 @@ struct MessageRowView: View {
24842487 var body: some View {
24852488 Label(text, systemImage: isError ? "exclamationmark.triangle.fill" : "info.circle")
24862489 .font(appDensity.font(.caption))
2487 .foregroundStyle(isError ? .red : .secondary)
2490 .foregroundStyle(isError ? Color(.statusCritical) : .secondary)
24882491 .lineLimit(nil)
24892492 .fixedSize(horizontal: false, vertical: true)
24902493 }
@@ -2576,6 +2579,11 @@ struct LabeledValueRow: View {
25762579 }
25772580}
25782581
2582/// Maps a row's semantic tone onto the app palette.
2583///
2584/// See `Docs/ACCESSIBILITY.md` for the measured contrast ratios behind these
2585/// colours. Never reach for a literal (`Color(.statusCritical)`, `Color(.statusWarning)`, …) — the system
2586/// palette fails WCAG AA badly in light mode (systemYellow is 1.28:1 on white).
25792587enum ResultColors {
25802588 static func color(for tone: ResultTone) -> Color {
25812589 switch tone {
@@ -2584,11 +2592,11 @@ enum ResultColors {
25842592 case .secondary:
25852593 return .secondary
25862594 case .success:
2587 return .green
2595 return Color(.statusPositive)
25882596 case .warning:
2589 return .yellow
2597 return Color(.statusWarning)
25902598 case .failure:
2591 return .red
2599 return Color(.statusCritical)
25922600 }
25932601 }
25942602}
@@ -2654,7 +2662,7 @@ struct SettingsView: View {
26542662 if let errorMessage = purchaseService.errorMessage {
26552663 Text(errorMessage)
26562664 .font(appDensity.font(.caption, design: .default))
2657 .foregroundStyle(.red)
2665 .foregroundStyle(Color(.statusCritical))
26582666 }
26592667 }
26602668
@@ -2789,7 +2797,7 @@ private struct HistoryNetworkSettingsView: View {
27892797 if let customResolverError {
27902798 Text(customResolverError)
27912799 .font(appDensity.font(.caption, design: .default))
2792 .foregroundStyle(.red)
2800 .foregroundStyle(Color(.statusCritical))
27932801 }
27942802 }
27952803 }
@@ -2871,7 +2879,7 @@ private struct CloudSyncSettingsView: View {
28712879 if let lastErrorMessage = cloudSyncService.lastErrorMessage {
28722880 Text(lastErrorMessage)
28732881 .font(appDensity.font(.caption, design: .default))
2874 .foregroundStyle(.red)
2882 .foregroundStyle(Color(.statusCritical))
28752883 }
28762884 }
28772885 }
@@ -2964,7 +2972,7 @@ private struct LocalAPISettingsView: View {
29642972 Spacer()
29652973 Text("\(log.statusCode)")
29662974 .font(appDensity.font(.caption, design: .default))
2967 .foregroundStyle(log.statusCode >= 400 ? .red : .secondary)
2975 .foregroundStyle(log.statusCode >= 400 ? Color(.statusCritical) : .secondary)
29682976 }
29692977
29702978 Text(log.timestamp.formatted(date: .abbreviated, time: .standard))
@@ -3744,8 +3752,8 @@ extension ChangeImpactClassification {
37443752 var color: Color {
37453753 switch self {
37463754 case .informational: return .secondary
3747 case .warning: return .yellow
3748 case .critical: return .red
3755 case .warning: return Color(.statusWarning)
3756 case .critical: return Color(.statusCritical)
37493757 }
37503758 }
37513759}
DomainDig/DashboardView.swift +31 −31
@@ -22,20 +22,20 @@ struct DashboardView: View {
2222 showsCardBackground: false
2323 )
2424 }
25 .listRowBackground(Color(.systemGray6).opacity(0.5))
25 .listRowBackground(Color(.appSurface))
2626 } else {
2727 Section {
2828 LazyVGrid(columns: summaryColumns, spacing: 10) {
29 summaryCard(title: "Total Domains", value: viewModel.portfolioDashboardData.snapshot.totalDomains, filter: .all, tint: .cyan)
30 summaryCard(title: "Healthy", value: viewModel.portfolioDashboardData.snapshot.healthyCount, filter: .healthy, tint: .green)
31 summaryCard(title: "Warning", value: viewModel.portfolioDashboardData.snapshot.warningCount, filter: .warning, tint: .yellow)
32 summaryCard(title: "Critical", value: viewModel.portfolioDashboardData.snapshot.criticalCount, filter: .critical, tint: .red)
33 summaryCard(title: "Changes (24h)", value: viewModel.portfolioDashboardData.snapshot.changedLast24h, filter: .changed, tint: .orange)
34 summaryCard(title: "Unreachable", value: viewModel.portfolioDashboardData.snapshot.unreachableCount, filter: .unreachable, tint: .pink)
29 summaryCard(title: "Total Domains", value: viewModel.portfolioDashboardData.snapshot.totalDomains, filter: .all, tint: Color(.statusInfo))
30 summaryCard(title: "Healthy", value: viewModel.portfolioDashboardData.snapshot.healthyCount, filter: .healthy, tint: Color(.statusPositive))
31 summaryCard(title: "Warning", value: viewModel.portfolioDashboardData.snapshot.warningCount, filter: .warning, tint: Color(.statusWarning))
32 summaryCard(title: "Critical", value: viewModel.portfolioDashboardData.snapshot.criticalCount, filter: .critical, tint: Color(.statusCritical))
33 summaryCard(title: "Changes (24h)", value: viewModel.portfolioDashboardData.snapshot.changedLast24h, filter: .changed, tint: Color(.statusWarning))
34 summaryCard(title: "Unreachable", value: viewModel.portfolioDashboardData.snapshot.unreachableCount, filter: .unreachable, tint: Color(.statusCritical))
3535 }
3636 .padding(.vertical, 4)
3737 }
38 .listRowBackground(Color(.systemGray6).opacity(0.5))
38 .listRowBackground(Color(.appSurface))
3939
4040 Section("Quick Filters") {
4141 ScrollView(.horizontal, showsIndicators: false) {
@@ -46,10 +46,10 @@ struct DashboardView: View {
4646 } label: {
4747 Text(filter.title)
4848 .font(appDensity.font(.caption, weight: .semibold))
49 .foregroundStyle(viewModel.dashboardFilter == filter ? Color.black : Color.white)
49 .foregroundStyle(viewModel.dashboardFilter == filter ? Color(.appOnAccent) : Color.primary)
5050 .padding(.horizontal, 12)
5151 .padding(.vertical, 8)
52 .background(viewModel.dashboardFilter == filter ? Color.cyan : Color(.systemGray5).opacity(0.6))
52 .background(viewModel.dashboardFilter == filter ? Color(.statusInfo) : Color(.appSurfaceElevated))
5353 .clipShape(Capsule())
5454 }
5555 .buttonStyle(.plain)
@@ -58,7 +58,7 @@ struct DashboardView: View {
5858 .padding(.vertical, 4)
5959 }
6060 }
61 .listRowBackground(Color(.systemGray6).opacity(0.5))
61 .listRowBackground(Color(.appSurface))
6262
6363 Section("Recent Activity") {
6464 if viewModel.filteredPortfolioRecentActivity.isEmpty {
@@ -76,7 +76,7 @@ struct DashboardView: View {
7676 }
7777 }
7878 }
79 .listRowBackground(Color(.systemGray6).opacity(0.5))
79 .listRowBackground(Color(.appSurface))
8080
8181 Section("Attention Required") {
8282 if viewModel.filteredPortfolioAttentionRequired.isEmpty {
@@ -94,7 +94,7 @@ struct DashboardView: View {
9494 }
9595 }
9696 }
97 .listRowBackground(Color(.systemGray6).opacity(0.5))
97 .listRowBackground(Color(.appSurface))
9898
9999 Section("Expiring Soon") {
100100 if viewModel.filteredPortfolioExpiringSoon.isEmpty {
@@ -110,7 +110,7 @@ struct DashboardView: View {
110110 }
111111 }
112112 }
113 .listRowBackground(Color(.systemGray6).opacity(0.5))
113 .listRowBackground(Color(.appSurface))
114114
115115 Section("Portfolio List") {
116116 if viewModel.filteredPortfolioGroups.isEmpty {
@@ -150,11 +150,11 @@ struct DashboardView: View {
150150 }
151151 }
152152 }
153 .listRowBackground(Color(.systemGray6).opacity(0.5))
153 .listRowBackground(Color(.appSurface))
154154 }
155155 }
156156 .scrollContentBackground(.hidden)
157 .background(Color.black)
157 .background(Color(.appBackground))
158158 .navigationTitle("Dashboard")
159159 .searchable(text: $viewModel.dashboardSearchText, prompt: "Search portfolio")
160160 .toolbar {
@@ -196,7 +196,7 @@ struct DashboardView: View {
196196 .foregroundStyle(.secondary)
197197 Text("\(value)")
198198 .font(.system(size: 28, weight: .bold, design: .rounded))
199 .foregroundStyle(.white)
199 .foregroundStyle(.primary)
200200 HStack {
201201 Circle()
202202 .fill(tint)
@@ -218,13 +218,13 @@ struct DashboardView: View {
218218 if viewModel.dashboardFilter == filter {
219219 return AnyShapeStyle(
220220 LinearGradient(
221 colors: [Color.cyan.opacity(0.28), Color.cyan.opacity(0.12)],
221 colors: [Color(.statusInfo).opacity(0.28), Color(.statusInfo).opacity(0.12)],
222222 startPoint: .topLeading,
223223 endPoint: .bottomTrailing
224224 )
225225 )
226226 }
227 return AnyShapeStyle(Color(.systemGray5).opacity(0.45))
227 return AnyShapeStyle(Color(.appSurfaceElevated))
228228 }
229229
230230 private func dashboardEmptyRow(_ message: String) -> some View {
@@ -243,15 +243,15 @@ struct DashboardView: View {
243243
244244 if criticalCount > 0 {
245245 title = "\(criticalCount) critical"
246 color = .red
246 color = Color(.statusCritical)
247247 systemImage = "exclamationmark.octagon.fill"
248248 } else if warningCount > 0 {
249249 title = "\(warningCount) warning"
250 color = .yellow
250 color = Color(.statusWarning)
251251 systemImage = "exclamationmark.triangle.fill"
252252 } else {
253253 title = "Healthy"
254 color = .green
254 color = Color(.statusPositive)
255255 systemImage = "checkmark.circle.fill"
256256 }
257257
@@ -297,11 +297,11 @@ private struct PortfolioActivityRow: View {
297297 private var iconColor: Color {
298298 switch item.health {
299299 case .healthy:
300 return .cyan
300 return Color(.statusInfo)
301301 case .warning:
302 return .yellow
302 return Color(.statusWarning)
303303 case .critical:
304 return .red
304 return Color(.statusCritical)
305305 }
306306 }
307307}
@@ -332,11 +332,11 @@ private struct PortfolioAttentionRow: View {
332332 private var badgeModel: AppStatusBadgeModel {
333333 switch item.health {
334334 case .healthy:
335 return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
335 return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
336336 case .warning:
337 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
337 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
338338 case .critical:
339 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
339 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
340340 }
341341 }
342342}
@@ -371,11 +371,11 @@ private struct PortfolioExpiryRow: View {
371371 private var badgeModel: AppStatusBadgeModel {
372372 switch state.certificateExpiryState {
373373 case .none:
374 return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
374 return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
375375 case .warning:
376 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
376 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
377377 case .critical:
378 return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
378 return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
379379 }
380380 }
381381}
DomainDig/DomainCompareView.swift +2 −2
@@ -63,11 +63,11 @@ struct DomainCompareView: View {
6363 showsCardBackground: false
6464 )
6565 }
66 .listRowBackground(Color(.systemGray6).opacity(0.5))
66 .listRowBackground(Color(.appSurface))
6767 }
6868 }
6969 .scrollContentBackground(.hidden)
70 .background(Color.black)
70 .background(Color(.appBackground))
7171 .navigationTitle("Compare Domains")
7272 }
7373
DomainDig/DomainDig/AuditViews.swift +24 −24
@@ -27,7 +27,7 @@ struct AuditListView: View {
2727 showsCardBackground: false
2828 )
2929 }
30 .listRowBackground(Color(.systemGray6).opacity(0.5))
30 .listRowBackground(Color(.appSurface))
3131 } else {
3232 Section("Audit Domains") {
3333 ForEach(groupedSessions, id: \.domain) { group in
@@ -65,11 +65,11 @@ struct AuditListView: View {
6565 }
6666 }
6767 }
68 .listRowBackground(Color(.systemGray6).opacity(0.5))
68 .listRowBackground(Color(.appSurface))
6969 }
7070 }
7171 .scrollContentBackground(.hidden)
72 .background(Color.black)
72 .background(Color(.appBackground))
7373 .navigationTitle("Audit Mode")
7474 .toolbar {
7575 if !viewModel.searchedDomain.isEmpty {
@@ -132,7 +132,7 @@ struct AuditDomainTimelineView: View {
132132 }
133133 }
134134 }
135 .listRowBackground(Color(.systemGray6).opacity(0.5))
135 .listRowBackground(Color(.appSurface))
136136
137137 Section("Trend") {
138138 ForEach(viewModel.auditTimeline(for: domain)) { point in
@@ -152,10 +152,10 @@ struct AuditDomainTimelineView: View {
152152 .padding(.vertical, 2)
153153 }
154154 }
155 .listRowBackground(Color(.systemGray6).opacity(0.5))
155 .listRowBackground(Color(.appSurface))
156156 }
157157 .scrollContentBackground(.hidden)
158 .background(Color.black)
158 .background(Color(.appBackground))
159159 .navigationTitle(domain)
160160 .toolbar {
161161 ToolbarItem(placement: .topBarTrailing) {
@@ -218,7 +218,7 @@ struct AuditSessionDetailView: View {
218218 .foregroundStyle(.secondary)
219219 }
220220 }
221 .listRowBackground(Color(.systemGray6).opacity(0.5))
221 .listRowBackground(Color(.appSurface))
222222
223223 Section("Audit Summary") {
224224 LabeledContent("Findings", value: "\(session.findings.count)")
@@ -228,7 +228,7 @@ struct AuditSessionDetailView: View {
228228 LabeledContent("Highest Severity", value: severity.title)
229229 }
230230 }
231 .listRowBackground(Color(.systemGray6).opacity(0.5))
231 .listRowBackground(Color(.appSurface))
232232
233233 Section("Reviewer Notes") {
234234 TextEditor(text: $notesDraft)
@@ -240,7 +240,7 @@ struct AuditSessionDetailView: View {
240240 viewModel.updateAuditNotes(notesDraft, sessionID: session.id)
241241 }
242242 }
243 .listRowBackground(Color(.systemGray6).opacity(0.5))
243 .listRowBackground(Color(.appSurface))
244244
245245 Section("Checklist") {
246246 ForEach(session.checklist) { item in
@@ -249,7 +249,7 @@ struct AuditSessionDetailView: View {
249249 } label: {
250250 HStack(alignment: .top, spacing: 10) {
251251 Image(systemName: item.isComplete ? "checkmark.circle.fill" : "circle")
252 .foregroundStyle(item.isComplete ? Color.green : .secondary)
252 .foregroundStyle(item.isComplete ? Color(.statusPositive) : .secondary)
253253 VStack(alignment: .leading, spacing: 4) {
254254 Text(item.title)
255255 .font(appDensity.font(.callout))
@@ -264,7 +264,7 @@ struct AuditSessionDetailView: View {
264264 .buttonStyle(.plain)
265265 }
266266 }
267 .listRowBackground(Color(.systemGray6).opacity(0.5))
267 .listRowBackground(Color(.appSurface))
268268
269269 Section("Findings") {
270270 if session.findings.isEmpty {
@@ -301,7 +301,7 @@ struct AuditSessionDetailView: View {
301301 }
302302 }
303303 }
304 .listRowBackground(Color(.systemGray6).opacity(0.5))
304 .listRowBackground(Color(.appSurface))
305305
306306 Section("Evidence Snapshot") {
307307 LabeledContent("Availability", value: availabilityTitle(session.evidence.report.availability))
@@ -311,7 +311,7 @@ struct AuditSessionDetailView: View {
311311 LabeledContent("Reachability", value: session.evidence.report.network.reachabilitySummary)
312312 LabeledContent("Registrar", value: session.evidence.report.ownership?.registrar ?? "Unavailable")
313313 }
314 .listRowBackground(Color(.systemGray6).opacity(0.5))
314 .listRowBackground(Color(.appSurface))
315315
316316 if !session.evidence.historicalContext.isEmpty {
317317 Section("Historical Context") {
@@ -325,7 +325,7 @@ struct AuditSessionDetailView: View {
325325 }
326326 }
327327 }
328 .listRowBackground(Color(.systemGray6).opacity(0.5))
328 .listRowBackground(Color(.appSurface))
329329 }
330330
331331 Section("Audit Timeline") {
@@ -345,10 +345,10 @@ struct AuditSessionDetailView: View {
345345 }
346346 }
347347 }
348 .listRowBackground(Color(.systemGray6).opacity(0.5))
348 .listRowBackground(Color(.appSurface))
349349 }
350350 .scrollContentBackground(.hidden)
351 .background(Color.black)
351 .background(Color(.appBackground))
352352 .navigationTitle("Audit Session")
353353 .toolbar {
354354 ToolbarItemGroup(placement: .topBarTrailing) {
@@ -398,7 +398,7 @@ struct AuditSessionDetailView: View {
398398 }
399399 } else {
400400 ContentUnavailableView("Audit Session Missing", systemImage: "exclamationmark.triangle")
401 .background(Color.black)
401 .background(Color(.appBackground))
402402 }
403403 }
404404 .preferredColorScheme(.dark)
@@ -477,7 +477,7 @@ private struct AuditFindingEditorView: View {
477477 Text(area.title)
478478 Spacer()
479479 Image(systemName: selectedAreas.contains(area) ? "checkmark.circle.fill" : "circle")
480 .foregroundStyle(selectedAreas.contains(area) ? Color.green : .secondary)
480 .foregroundStyle(selectedAreas.contains(area) ? Color(.statusPositive) : .secondary)
481481 }
482482 }
483483 .buttonStyle(.plain)
@@ -528,11 +528,11 @@ private func auditStatusBadge(_ status: AuditStatus) -> some View {
528528 let model: AppStatusBadgeModel
529529 switch status {
530530 case .draft:
531 model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
531 model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
532532 case .inReview:
533 model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16))
533 model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
534534 case .complete:
535 model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
535 model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
536536 }
537537 return AppStatusBadgeView(model: model)
538538}
@@ -543,11 +543,11 @@ private func findingSeverityBadge(_ severity: AuditFindingSeverity) -> some View
543543 case .informational:
544544 color = .secondary
545545 case .low:
546 color = .green
546 color = Color(.statusPositive)
547547 case .medium:
548 color = .yellow
548 color = Color(.statusWarning)
549549 case .high:
550 color = .red
550 color = Color(.statusCritical)
551551 }
552552 return AppStatusBadgeView(
553553 model: .init(
DomainDig/DomainDigUI.swift +21 −21
@@ -92,48 +92,48 @@ enum AppStatusFactory {
9292 static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel {
9393 switch status {
9494 case .available:
95 return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
95 return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
9696 case .registered:
97 return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
97 return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
9898 case .unknown, .none:
99 return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
99 return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
100100 }
101101 }
102102
103103 static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel {
104104 if error != nil || sslInfo == nil {
105 return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
105 return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
106106 }
107107 if let sslInfo, sslInfo.daysUntilExpiry <= 14 {
108 return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
108 return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
109109 }
110 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
110 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
111111 }
112112
113113 static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel {
114114 guard error == nil, let result else {
115 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
115 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
116116 }
117117
118118 let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count
119119 switch foundCount {
120120 case 3:
121 return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
121 return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
122122 case 1, 2:
123 return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
123 return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
124124 default:
125 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
125 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
126126 }
127127 }
128128
129129 static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel {
130130 guard let summary else {
131 return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
131 return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
132132 }
133133 if summary.hasChanges {
134 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16))
134 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
135135 }
136 return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
136 return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
137137 }
138138}
139139
@@ -184,9 +184,9 @@ struct AppCopyButton: View {
184184 } label: {
185185 Image(systemName: didCopy ? "checkmark" : "doc.on.doc")
186186 .font(appDensity.font(.caption))
187 .foregroundStyle(didCopy ? Color.green : .secondary)
187 .foregroundStyle(didCopy ? Color(.statusPositive) : .secondary)
188188 .frame(width: 30, height: 30)
189 .background(Color(.systemGray5).opacity(0.35))
189 .background(Color(.appSurfaceElevated))
190190 .clipShape(RoundedRectangle(cornerRadius: 8))
191191 }
192192 .buttonStyle(.plain)
@@ -264,11 +264,11 @@ struct EmptyStateCardView: View {
264264
265265 Text(suggestion)
266266 .font(appDensity.font(.caption))
267 .foregroundStyle(.cyan)
267 .foregroundStyle(Color(.statusInfo))
268268 }
269269 .frame(maxWidth: .infinity, alignment: .leading)
270270 .padding(appDensity.metrics.cardPadding)
271 .background(showsCardBackground ? Color(.systemGray6).opacity(0.45) : Color.clear)
271 .background(showsCardBackground ? Color(.appSurface) : Color.clear)
272272 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
273273 }
274274}
@@ -307,7 +307,7 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View {
307307 VStack(alignment: .leading, spacing: 3) {
308308 Text(title)
309309 .font(appDensity.font(.headline, design: .default, weight: .semibold))
310 .foregroundStyle(.white)
310 .foregroundStyle(.primary)
311311 if let subtitle {
312312 Text(subtitle)
313313 .font(appDensity.font(.caption))
@@ -346,7 +346,7 @@ struct TagChipRowView: View {
346346 .font(.caption)
347347 .padding(.horizontal, 10)
348348 .padding(.vertical, 5)
349 .background(Color(.systemGray5).opacity(0.6), in: Capsule())
349 .background(Color(.appSurfaceElevated), in: Capsule())
350350 }
351351 }
352352 }
@@ -380,8 +380,8 @@ struct TagFilterChipRowView: View {
380380 .font(.caption)
381381 .padding(.horizontal, 10)
382382 .padding(.vertical, 5)
383 .background(isSelected ? Color.cyan.opacity(0.3) : Color(.systemGray5).opacity(0.6), in: Capsule())
384 .foregroundStyle(isSelected ? Color.cyan : Color.primary)
383 .background(isSelected ? Color(.statusInfo).opacity(0.3) : Color(.appSurfaceElevated), in: Capsule())
384 .foregroundStyle(isSelected ? Color(.statusInfo) : Color.primary)
385385 }
386386 .buttonStyle(.plain)
387387 }
DomainDig/HistoryView.swift +9 −9
@@ -25,7 +25,7 @@ struct HistoryView: View {
2525 systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90",
2626 showsCardBackground: false
2727 )
28 .listRowBackground(Color(.systemGray6).opacity(0.5))
28 .listRowBackground(Color(.appSurface))
2929 } else {
3030 Section("Domains") {
3131 ForEach(domainSummaries, id: \.domain) { item in
@@ -55,21 +55,21 @@ struct HistoryView: View {
5555 model: .init(
5656 title: severity.title,
5757 systemImage: "arrow.triangle.2.circlepath",
58 foregroundColor: severity == .high ? .red : .yellow,
59 backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16)
58 foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning),
59 backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16)
6060 )
6161 )
6262 }
6363 }
6464 }
6565 }
66 .listRowBackground(Color(.systemGray6).opacity(0.5))
66 .listRowBackground(Color(.appSurface))
6767 }
6868 }
6969 }
7070 }
7171 .scrollContentBackground(.hidden)
72 .background(Color.black)
72 .background(Color(.appBackground))
7373 .navigationTitle("History")
7474 .searchable(text: $viewModel.timelineDomainFilter, prompt: "Search domains")
7575 .toolbar {
@@ -321,7 +321,7 @@ struct HistoryDetailView: View {
321321 .padding(.horizontal)
322322 .padding(.bottom, 32)
323323 }
324 .background(Color.black)
324 .background(Color(.appBackground))
325325 .navigationTitle(entry.domain)
326326 .toolbar {
327327 ToolbarItemGroup(placement: .topBarTrailing) {
@@ -390,12 +390,12 @@ struct HistoryDetailView: View {
390390 if let mismatchNote = viewModel.resolverMismatchNote(for: entry) {
391391 Text(mismatchNote)
392392 .font(appDensity.font(.caption2))
393 .foregroundStyle(.orange)
393 .foregroundStyle(Color(.statusWarning))
394394 }
395395 if entry.isPartialSnapshot {
396396 Text("Partial snapshot: \(entry.validationIssues.joined(separator: " | "))")
397397 .font(appDensity.font(.caption2))
398 .foregroundStyle(.yellow)
398 .foregroundStyle(Color(.statusWarning))
399399 }
400400 if let note = entry.note, !note.isEmpty {
401401 Text(note)
@@ -406,7 +406,7 @@ struct HistoryDetailView: View {
406406 .foregroundStyle(.secondary)
407407 .padding(8)
408408 .frame(maxWidth: .infinity, alignment: .leading)
409 .background(Color(.systemGray6).opacity(0.3))
409 .background(Color(.appSurface))
410410 .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
411411 .padding(.vertical, 12)
412412 }
DomainDig/IntegrationsView.swift +3 −3
@@ -52,7 +52,7 @@ struct IntegrationsSettingsView: View {
5252 if !target.isEnabled {
5353 Text("Disabled")
5454 .font(.caption2)
55 .foregroundStyle(.orange)
55 .foregroundStyle(Color(.statusWarning))
5656 }
5757 }
5858 }
@@ -158,7 +158,7 @@ private struct IntegrationDetailView: View {
158158 .foregroundStyle(.secondary)
159159
160160 if let failureReason = record.failureReason {
161 let failureColor: Color = record.status == .skipped ? .secondary : .red
161 let failureColor: Color = record.status == .skipped ? .secondary : Color(.statusCritical)
162162 Text(failureReason)
163163 .font(.caption)
164164 .foregroundStyle(failureColor)
@@ -324,7 +324,7 @@ private struct IntegrationEditorView: View {
324324 Section {
325325 Text(validationMessage)
326326 .font(.caption)
327 .foregroundStyle(.red)
327 .foregroundStyle(Color(.statusCritical))
328328 }
329329 }
330330 }
DomainDig/MonitoringView.swift +14 −14
@@ -41,7 +41,7 @@ struct MonitoringView: View {
4141 }
4242 .padding(.vertical, 4)
4343 }
44 .listRowBackground(Color(.systemGray6).opacity(0.5))
44 .listRowBackground(Color(.appSurface))
4545
4646 if viewModel.monitoringLogs.isEmpty {
4747 Section {
@@ -53,7 +53,7 @@ struct MonitoringView: View {
5353 showsCardBackground: false
5454 )
5555 }
56 .listRowBackground(Color(.systemGray6).opacity(0.5))
56 .listRowBackground(Color(.appSurface))
5757 } else {
5858 Section("Recent Runs") {
5959 ForEach(viewModel.monitoringLogs) { log in
@@ -78,10 +78,10 @@ struct MonitoringView: View {
7878 HStack(spacing: 8) {
7979 metricBadge(title: "\(log.domainsChecked) checked")
8080 if log.changesFound > 0 {
81 metricBadge(title: "\(log.changesFound) changed", tint: .orange)
81 metricBadge(title: "\(log.changesFound) changed", tint: Color(.statusWarning))
8282 }
8383 if log.alertsTriggered > 0 {
84 metricBadge(title: "\(log.alertsTriggered) alerts", tint: .red)
84 metricBadge(title: "\(log.alertsTriggered) alerts", tint: Color(.statusCritical))
8585 }
8686 }
8787 }
@@ -89,11 +89,11 @@ struct MonitoringView: View {
8989 }
9090 }
9191 }
92 .listRowBackground(Color(.systemGray6).opacity(0.5))
92 .listRowBackground(Color(.appSurface))
9393 }
9494 }
9595 .scrollContentBackground(.hidden)
96 .background(Color.black)
96 .background(Color(.appBackground))
9797 .navigationTitle("Monitoring")
9898 .preferredColorScheme(.dark)
9999 .onAppear {
@@ -101,7 +101,7 @@ struct MonitoringView: View {
101101 }
102102 }
103103
104 private func metricBadge(title: String, tint: Color = .cyan) -> some View {
104 private func metricBadge(title: String, tint: Color = Color(.statusInfo)) -> some View {
105105 Text(title)
106106 .font(appDensity.font(.caption2))
107107 .foregroundStyle(tint)
@@ -133,7 +133,7 @@ struct MonitoringLogDetailView: View {
133133 LabeledContent("Alerts", value: "\(log.alertsTriggered)")
134134 LabeledContent("Timestamp", value: log.timestamp.formatted(date: .abbreviated, time: .shortened))
135135 }
136 .listRowBackground(Color(.systemGray6).opacity(0.5))
136 .listRowBackground(Color(.appSurface))
137137
138138 Section("Domains") {
139139 ForEach(log.checkedDomains) { result in
@@ -167,7 +167,7 @@ struct MonitoringLogDetailView: View {
167167 if let errorMessage = result.errorMessage {
168168 Text(errorMessage)
169169 .font(appDensity.font(.caption2))
170 .foregroundStyle(.yellow)
170 .foregroundStyle(Color(.statusWarning))
171171 }
172172
173173 if let historyEntryID = result.historyEntryID,
@@ -181,7 +181,7 @@ struct MonitoringLogDetailView: View {
181181 .padding(.vertical, 4)
182182 }
183183 }
184 .listRowBackground(Color(.systemGray6).opacity(0.5))
184 .listRowBackground(Color(.appSurface))
185185
186186 if !log.errors.isEmpty {
187187 Section("Errors") {
@@ -191,11 +191,11 @@ struct MonitoringLogDetailView: View {
191191 .foregroundStyle(.secondary)
192192 }
193193 }
194 .listRowBackground(Color(.systemGray6).opacity(0.5))
194 .listRowBackground(Color(.appSurface))
195195 }
196196 }
197197 .scrollContentBackground(.hidden)
198 .background(Color.black)
198 .background(Color(.appBackground))
199199 .navigationTitle("Run Details")
200200 .preferredColorScheme(.dark)
201201 }
@@ -205,9 +205,9 @@ struct MonitoringLogDetailView: View {
205205 case .info:
206206 return .secondary
207207 case .warning:
208 return .yellow
208 return Color(.statusWarning)
209209 case .critical:
210 return .red
210 return Color(.statusCritical)
211211 }
212212 }
213213}
DomainDig/PaywallView.swift +1 −1
@@ -75,7 +75,7 @@ struct PaywallView: View {
7575 if let errorMessage = purchaseService.errorMessage {
7676 Text(errorMessage)
7777 .font(appDensity.font(.caption, design: .default))
78 .foregroundStyle(.red)
78 .foregroundStyle(Color(.statusCritical))
7979 }
8080 }
8181
DomainDig/SavedDomainsView.swift +3 −3
@@ -10,7 +10,7 @@ struct SavedDomainsView: View {
1010 Text("No saved domains")
1111 .font(.system(.callout, design: .monospaced))
1212 .foregroundStyle(.secondary)
13 .listRowBackground(Color(.systemGray6).opacity(0.5))
13 .listRowBackground(Color(.appSurface))
1414 } else {
1515 ForEach(viewModel.savedDomains, id: \.self) { domain in
1616 Button {
@@ -22,7 +22,7 @@ struct SavedDomainsView: View {
2222 .font(.system(.callout, design: .monospaced))
2323 .foregroundStyle(.primary)
2424 }
25 .listRowBackground(Color(.systemGray6).opacity(0.5))
25 .listRowBackground(Color(.appSurface))
2626 }
2727 .onDelete { offsets in
2828 viewModel.removeSavedDomains(at: offsets)
@@ -30,7 +30,7 @@ struct SavedDomainsView: View {
3030 }
3131 }
3232 .scrollContentBackground(.hidden)
33 .background(Color.black)
33 .background(Color(.appBackground))
3434 .navigationTitle("Saved Domains")
3535 .toolbar {
3636 if !viewModel.savedDomains.isEmpty {
DomainDig/ScheduledReportsView.swift +4 −4
@@ -67,7 +67,7 @@ struct ScheduledReportsView: View {
6767 }
6868 .disabled(isGenerating || !FeatureAccessService.hasAccess(to: .automatedMonitoring))
6969 }
70 .listRowBackground(Color(.systemGray6).opacity(0.5))
70 .listRowBackground(Color(.appSurface))
7171
7272 if logs.isEmpty {
7373 Section {
@@ -79,7 +79,7 @@ struct ScheduledReportsView: View {
7979 showsCardBackground: false
8080 )
8181 }
82 .listRowBackground(Color(.systemGray6).opacity(0.5))
82 .listRowBackground(Color(.appSurface))
8383 } else {
8484 Section("Recent Reports") {
8585 ForEach(logs) { log in
@@ -96,11 +96,11 @@ struct ScheduledReportsView: View {
9696 }
9797 }
9898 }
99 .listRowBackground(Color(.systemGray6).opacity(0.5))
99 .listRowBackground(Color(.appSurface))
100100 }
101101 }
102102 .scrollContentBackground(.hidden)
103 .background(Color.black)
103 .background(Color(.appBackground))
104104 .navigationTitle("Scheduled Reports")
105105 }
106106
DomainDig/TimelineView.swift +5 −5
@@ -45,12 +45,12 @@ struct TimelineView: View {
4545 }
4646 }
4747 }
48 .listRowBackground(Color(.systemGray6).opacity(0.5))
48 .listRowBackground(Color(.appSurface))
4949 }
5050 }
5151 }
5252 .scrollContentBackground(.hidden)
53 .background(Color.black)
53 .background(Color(.appBackground))
5454 .navigationTitle(domain)
5555 .toolbar {
5656 ToolbarItemGroup(placement: .topBarTrailing) {
@@ -116,8 +116,8 @@ private struct TimelineRow: View {
116116 model: .init(
117117 title: severity.title,
118118 systemImage: "arrow.triangle.2.circlepath",
119 foregroundColor: severity == .high ? .red : .yellow,
120 backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16)
119 foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning),
120 backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16)
121121 )
122122 )
123123 }
@@ -203,7 +203,7 @@ struct TimelineDiffView: View {
203203 }
204204 .padding()
205205 }
206 .background(Color.black)
206 .background(Color(.appBackground))
207207 .navigationTitle("Compare Snapshots")
208208 .navigationBarTitleDisplayMode(.inline)
209209 .onAppear {
DomainDig/WatchlistView.swift +23 −23
@@ -38,7 +38,7 @@ struct WatchlistView: View {
3838 Section("Refresh Progress") {
3939 VStack(alignment: .leading, spacing: 8) {
4040 ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1)))
41 .tint(.cyan)
41 .tint(Color(.statusInfo))
4242 HStack {
4343 Text(viewModel.batchProgressLabel)
4444 .font(appDensity.font(.caption))
@@ -59,7 +59,7 @@ struct WatchlistView: View {
5959 }
6060 .padding(.vertical, 4)
6161 }
62 .listRowBackground(Color(.systemGray6).opacity(0.5))
62 .listRowBackground(Color(.appSurface))
6363 }
6464
6565 if viewModel.filteredTrackedDomains.isEmpty {
@@ -72,7 +72,7 @@ struct WatchlistView: View {
7272 showsCardBackground: false
7373 )
7474 }
75 .listRowBackground(Color(.systemGray6).opacity(0.5))
75 .listRowBackground(Color(.appSurface))
7676 } else {
7777 if let limitMessage = FeatureAccessService.trackedDomainLimitMessage(currentCount: viewModel.trackedDomains.count) {
7878 Section {
@@ -80,7 +80,7 @@ struct WatchlistView: View {
8080 .font(appDensity.font(.caption))
8181 .foregroundStyle(.secondary)
8282 }
83 .listRowBackground(Color(.systemGray6).opacity(0.5))
83 .listRowBackground(Color(.appSurface))
8484 }
8585
8686 if !pinnedDomains.isEmpty {
@@ -94,7 +94,7 @@ struct WatchlistView: View {
9494 }
9595 .animation(.easeInOut(duration: 0.2), value: viewModel.filteredTrackedDomains.map(\.id))
9696 .scrollContentBackground(.hidden)
97 .background(Color.black)
97 .background(Color(.appBackground))
9898 .navigationTitle("Watchlist")
9999 .searchable(text: $viewModel.watchlistSearchText, prompt: "Search tracked domains")
100100 .toolbar {
@@ -215,7 +215,7 @@ struct WatchlistView: View {
215215 Section {
216216 Text(addDomainError)
217217 .font(appDensity.font(.caption))
218 .foregroundStyle(.red)
218 .foregroundStyle(Color(.statusCritical))
219219 }
220220 }
221221 }
@@ -317,14 +317,14 @@ struct WatchlistView: View {
317317 } label: {
318318 Label("Refresh", systemImage: "arrow.clockwise")
319319 }
320 .tint(.cyan)
320 .tint(Color(.statusInfo))
321321
322322 Button {
323323 viewModel.togglePinned(for: trackedDomain)
324324 } label: {
325325 Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin")
326326 }
327 .tint(.yellow)
327 .tint(Color(.statusWarning))
328328 }
329329 .swipeActions(edge: .trailing, allowsFullSwipe: false) {
330330 Button {
@@ -333,7 +333,7 @@ struct WatchlistView: View {
333333 } label: {
334334 Label("Refresh", systemImage: "arrow.clockwise")
335335 }
336 .tint(.cyan)
336 .tint(Color(.statusInfo))
337337
338338 if viewModel.canDelete(trackedDomain) {
339339 Button(role: .destructive) {
@@ -380,7 +380,7 @@ struct WatchlistView: View {
380380 }
381381 }
382382 }
383 .listRowBackground(Color(.systemGray6).opacity(0.5))
383 .listRowBackground(Color(.appSurface))
384384 }
385385
386386 private var batchSummaryBinding: Binding<BatchSweepSummary?> {
@@ -437,7 +437,7 @@ struct WatchlistRowView: View {
437437 if trackedDomain.isPinned {
438438 Image(systemName: "pin.fill")
439439 .font(.caption2)
440 .foregroundStyle(.yellow)
440 .foregroundStyle(Color(.statusWarning))
441441 }
442442 Text(trackedDomain.domain)
443443 .font(appDensity.font(.callout))
@@ -502,7 +502,7 @@ struct WatchlistRowView: View {
502502 @ViewBuilder
503503 private var statusBadge: some View {
504504 if isRefreshing {
505 AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.6)))
505 AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)))
506506 } else {
507507 AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability))
508508 }
@@ -516,8 +516,8 @@ struct WatchlistRowView: View {
516516 model: .init(
517517 title: "Shared",
518518 systemImage: "person.2.fill",
519 foregroundColor: .cyan,
520 backgroundColor: .cyan.opacity(0.16)
519 foregroundColor: Color(.statusInfo),
520 backgroundColor: Color(.statusInfo).opacity(0.16)
521521 )
522522 )
523523 }
@@ -534,11 +534,11 @@ struct WatchlistRowView: View {
534534 let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon"
535535 switch trackedDomain.certificateWarningLevel {
536536 case .critical:
537 return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
537 return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
538538 case .warning:
539 return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
539 return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
540540 case .none:
541 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
541 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))
542542 }
543543 }
544544}
@@ -577,7 +577,7 @@ struct TrackedDomainDetailView: View {
577577 isRefreshing: viewModel.refreshingTrackedDomainID == liveTrackedDomain.id
578578 )
579579 }
580 .listRowBackground(Color(.systemGray6).opacity(0.5))
580 .listRowBackground(Color(.appSurface))
581581
582582 Section {
583583 Button {
@@ -652,13 +652,13 @@ struct TrackedDomainDetailView: View {
652652 Label(liveTrackedDomain.collaboration?.isShared == true ? "Manage Share" : "Share Domain", systemImage: "person.2")
653653 }
654654 }
655 .listRowBackground(Color(.systemGray6).opacity(0.5))
655 .listRowBackground(Color(.appSurface))
656656
657657 if !liveTrackedDomain.tags.isEmpty {
658658 Section("Tags") {
659659 TagChipRowView(tags: liveTrackedDomain.tags)
660660 }
661 .listRowBackground(Color(.systemGray6).opacity(0.5))
661 .listRowBackground(Color(.appSurface))
662662 }
663663
664664 Section("Monitoring Status") {
@@ -672,7 +672,7 @@ struct TrackedDomainDetailView: View {
672672 LabeledContent("Queued Alerts", value: "\(liveTrackedDomain.pendingMonitoringAlerts.count)")
673673 }
674674 }
675 .listRowBackground(Color(.systemGray6).opacity(0.5))
675 .listRowBackground(Color(.appSurface))
676676
677677 if let summary = viewModel.latestChangeSummary(for: liveTrackedDomain) {
678678 Section("Latest Change Summary") {
@@ -718,10 +718,10 @@ struct TrackedDomainDetailView: View {
718718 }
719719 }
720720 }
721 .listRowBackground(Color(.systemGray6).opacity(0.5))
721 .listRowBackground(Color(.appSurface))
722722 }
723723 .scrollContentBackground(.hidden)
724 .background(Color.black)
724 .background(Color(.appBackground))
725725 .navigationTitle(liveTrackedDomain.domain)
726726 .preferredColorScheme(.dark)
727727 .onChange(of: viewModel.rerunNavigationToken) { _, _ in
DomainDig/WorkflowsView.swift +9 −9
@@ -24,13 +24,13 @@ struct WorkflowsView: View {
2424 .font(appDensity.font(.caption))
2525 .foregroundStyle(.secondary)
2626 }
27 .listRowBackground(Color(.systemGray6).opacity(0.5))
27 .listRowBackground(Color(.appSurface))
2828 }
2929
3030 workflowContent
3131 }
3232 .scrollContentBackground(.hidden)
33 .background(Color.black)
33 .background(Color(.appBackground))
3434 .refreshable {
3535 await viewModel.refreshWorkflowList()
3636 }
@@ -73,7 +73,7 @@ struct WorkflowsView: View {
7373 value: Double(viewModel.batchCompletedCount),
7474 total: Double(max(viewModel.batchTotalCount, 1))
7575 )
76 .tint(.cyan)
76 .tint(Color(.statusInfo))
7777
7878 HStack {
7979 Text(viewModel.batchProgressLabel)
@@ -91,7 +91,7 @@ struct WorkflowsView: View {
9191 }
9292 .padding(.vertical, 4)
9393 }
94 .listRowBackground(Color(.systemGray6).opacity(0.5))
94 .listRowBackground(Color(.appSurface))
9595 }
9696
9797 if viewModel.workflows.isEmpty {
@@ -104,7 +104,7 @@ struct WorkflowsView: View {
104104 showsCardBackground: false
105105 )
106106 }
107 .listRowBackground(Color(.systemGray6).opacity(0.5))
107 .listRowBackground(Color(.appSurface))
108108 } else {
109109 ForEach(viewModel.workflows) { workflow in
110110 NavigationLink {
@@ -112,7 +112,7 @@ struct WorkflowsView: View {
112112 } label: {
113113 WorkflowRowView(workflow: workflow)
114114 }
115 .listRowBackground(Color(.systemGray6).opacity(0.5))
115 .listRowBackground(Color(.appSurface))
116116 }
117117 .onDelete { offsets in
118118 let workflows = offsets.map { viewModel.workflows[$0] }
@@ -133,7 +133,7 @@ private struct WorkflowRowView: View {
133133 if workflow.collaboration?.isShared == true {
134134 Image(systemName: "person.2.fill")
135135 .font(.caption2)
136 .foregroundStyle(.cyan)
136 .foregroundStyle(Color(.statusInfo))
137137 }
138138 Text(workflow.name)
139139 .font(appDensity.font(.callout, design: .default, weight: .semibold))
@@ -335,7 +335,7 @@ struct WorkflowDetailView: View {
335335 }
336336 }
337337 .scrollContentBackground(.hidden)
338 .background(Color.black)
338 .background(Color(.appBackground))
339339 .navigationTitle(workflow.name)
340340 .toolbar {
341341 if !workflow.domains.isEmpty {
@@ -640,7 +640,7 @@ struct WorkflowBulkAddSheet: View {
640640 .foregroundStyle(.primary)
641641 Spacer()
642642 Image(systemName: selectedDomains.contains(domain) ? "checkmark.circle.fill" : "circle")
643 .foregroundStyle(selectedDomains.contains(domain) ? .cyan : .secondary)
643 .foregroundStyle(selectedDomains.contains(domain) ? Color(.statusInfo) : .secondary)
644644 }
645645 }
646646 .buttonStyle(.plain)
DomainDigUITests/AccessibilityAuditHarness.swift +7 −1
@@ -87,7 +87,13 @@ enum AccessibilityAuditHarness {
8787 try app.performAccessibilityAudit { issue in
8888 let isEnforced = !enforcedAuditTypes.intersection(issue.auditType).isEmpty
8989 let marker = isEnforced ? "FAIL" : "report"
90 findings.append("[\(marker)][\(name(for: issue.auditType))] \(issue.compactDescription)")
90 // Include the element so the burndown says *what* to fix, not
91 // just that something is wrong.
92 let element = issue.element.map { el -> String in
93 let label = el.label.isEmpty ? el.identifier : el.label
94 return label.isEmpty ? "\(el.elementType)" : "\"\(label)\""
95 } ?? "unknown element"
96 findings.append("[\(marker)][\(name(for: issue.auditType))] \(issue.compactDescription) — \(element)")
9197 // true suppresses the finding, false reports it as a test failure.
9298 return !isEnforced
9399 }
DomainDigUITests/AccessibilityAuditTests.swift +14 −1
@@ -17,7 +17,20 @@ final class AccessibilityAuditTests: XCTestCase {
1717 // MARK: Per-screen audits
1818
1919 func testInspectScreen() throws {
20 try auditRootTab("Inspect")
20 let app = AccessibilityAuditHarness.launch()
21 app.selectRootTab("Inspect")
22
23 // Type a domain so the Run button is enabled. A disabled control has no
24 // contrast requirement under WCAG 1.4.3, but the audit still flags it,
25 // so auditing the empty state would report a false positive forever.
26 let field = app.textFields.firstMatch
27 if field.waitForExistence(timeout: 5) {
28 field.tap()
29 field.typeText("example.com")
30 }
31
32 let audited = try AccessibilityAuditHarness.audit(app, screen: "inspect", test: self)
33 try XCTSkipUnless(audited, "Audit did not complete in time for Inspect")
2134 }
2235
2336 func testDashboardScreen() throws {
DomainDigWidget/DomainDigPortfolioWidget.swift +10 −10
@@ -91,9 +91,9 @@ struct DomainDigWidgetView: View {
9191 Spacer(minLength: 0)
9292
9393 HStack(spacing: 10) {
94 countPill(data.healthyCount, .green)
95 countPill(data.warningCount, .orange)
96 countPill(data.criticalCount, .red)
94 countPill(data.healthyCount, Color(.statusPositive))
95 countPill(data.warningCount, Color(.statusWarning))
96 countPill(data.criticalCount, Color(.statusCritical))
9797 }
9898 }
9999 }
@@ -121,10 +121,10 @@ struct DomainDigWidgetView: View {
121121 }
122122
123123 HStack(spacing: 12) {
124 summaryStat(data.healthyCount, "Healthy", .green)
125 summaryStat(data.warningCount, "Warning", .orange)
126 summaryStat(data.criticalCount, "Critical", .red)
127 summaryStat(data.expiringSoonCount, "Expiring", .yellow)
124 summaryStat(data.healthyCount, "Healthy", Color(.statusPositive))
125 summaryStat(data.warningCount, "Warning", Color(.statusWarning))
126 summaryStat(data.criticalCount, "Critical", Color(.statusCritical))
127 summaryStat(data.expiringSoonCount, "Expiring", Color(.statusWarning))
128128 }
129129
130130 Divider()
@@ -180,9 +180,9 @@ struct DomainDigWidgetView: View {
180180
181181 private func color(for status: DomainDigWidgetStatus) -> Color {
182182 switch status {
183 case .healthy: return .green
184 case .warning: return .orange
185 case .critical: return .red
183 case .healthy: return Color(.statusPositive)
184 case .warning: return Color(.statusWarning)
185 case .critical: return Color(.statusCritical)
186186 }
187187 }
188188}
DomainDigWidget/SweepLiveActivity.swift +6 −6
@@ -24,7 +24,7 @@ struct SweepLiveActivity: Widget {
2424 DynamicIslandExpandedRegion(.bottom) {
2525 VStack(alignment: .leading, spacing: 4) {
2626 ProgressView(value: context.state.fractionComplete)
27 .tint(.cyan)
27 .tint(Color(.statusInfo))
2828 if let current = context.state.currentDomain {
2929 Text(current)
3030 .font(.caption2)
@@ -35,7 +35,7 @@ struct SweepLiveActivity: Widget {
3535 }
3636 } compactLeading: {
3737 Image(systemName: "arrow.trianglehead.2.clockwise")
38 .foregroundStyle(.cyan)
38 .foregroundStyle(Color(.statusInfo))
3939 } compactTrailing: {
4040 Text("\(context.state.completed)/\(context.state.total)")
4141 .font(.caption2)
@@ -43,7 +43,7 @@ struct SweepLiveActivity: Widget {
4343 } minimal: {
4444 ProgressView(value: context.state.fractionComplete)
4545 .progressViewStyle(.circular)
46 .tint(.cyan)
46 .tint(Color(.statusInfo))
4747 }
4848 }
4949 }
@@ -66,7 +66,7 @@ private struct SweepActivityBannerView: View {
6666 }
6767
6868 ProgressView(value: context.state.fractionComplete)
69 .tint(.cyan)
69 .tint(Color(.statusInfo))
7070
7171 HStack {
7272 if let current = context.state.currentDomain {
@@ -83,12 +83,12 @@ private struct SweepActivityBannerView: View {
8383 if context.state.changed > 0 {
8484 Text("\(context.state.changed) changed")
8585 .font(.caption2)
86 .foregroundStyle(.orange)
86 .foregroundStyle(Color(.statusWarning))
8787 }
8888 if context.state.warnings > 0 {
8989 Text("\(context.state.warnings) warnings")
9090 .font(.caption2)
91 .foregroundStyle(.red)
91 .foregroundStyle(Color(.statusCritical))
9292 }
9393 }
9494 }
Shared/Colors.xcassets/AccentFill.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0x00",
9 "green": "0x00",
10 "blue": "0xFF",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x00",
27 "green": "0x40",
28 "blue": "0xDD",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x00",
45 "green": "0x00",
46 "blue": "0xCC",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0x00",
67 "green": "0x33",
68 "blue": "0xCC",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/AppBackground.colorset/Contents.json added +38
@@ -0,0 +1,38 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0xFF",
9 "green": "0xFF",
10 "blue": "0xFF",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x00",
27 "green": "0x00",
28 "blue": "0x00",
29 "alpha": "1.000"
30 }
31 }
32 }
33 ],
34 "info": {
35 "author": "xcode",
36 "version": 1
37 }
38}
Shared/Colors.xcassets/AppOnAccent.colorset/Contents.json added +38
@@ -0,0 +1,38 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0xFF",
9 "green": "0xFF",
10 "blue": "0xFF",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x00",
27 "green": "0x00",
28 "blue": "0x00",
29 "alpha": "1.000"
30 }
31 }
32 }
33 ],
34 "info": {
35 "author": "xcode",
36 "version": 1
37 }
38}
Shared/Colors.xcassets/AppSeparator.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0xC6",
9 "green": "0xC6",
10 "blue": "0xC8",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x38",
27 "green": "0x38",
28 "blue": "0x3A",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0xA0",
45 "green": "0xA0",
46 "blue": "0xA3",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0x54",
67 "green": "0x54",
68 "blue": "0x56",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/AppSurface.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0xF2",
9 "green": "0xF2",
10 "blue": "0xF7",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x0E",
27 "green": "0x0E",
28 "blue": "0x0F",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0xEB",
45 "green": "0xEB",
46 "blue": "0xF0",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0x16",
67 "green": "0x16",
68 "blue": "0x18",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/AppSurfaceElevated.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0xE5",
9 "green": "0xE5",
10 "blue": "0xEA",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x1A",
27 "green": "0x1A",
28 "blue": "0x1C",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0xDC",
45 "green": "0xDC",
46 "blue": "0xE1",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0x23",
67 "green": "0x23",
68 "blue": "0x26",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/Contents.json added +6
@@ -0,0 +1,6 @@
1{
2 "info" : {
3 "author" : "xcode",
4 "version" : 1
5 }
6}
Shared/Colors.xcassets/StatusCritical.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0xB3",
9 "green": "0x26",
10 "blue": "0x1E",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0xFF",
27 "green": "0x69",
28 "blue": "0x61",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x8C",
45 "green": "0x1D",
46 "blue": "0x16",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0xFF",
67 "green": "0x9A",
68 "blue": "0x94",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/StatusInfo.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0x00",
9 "green": "0x00",
10 "blue": "0xFF",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x4D",
27 "green": "0xA3",
28 "blue": "0xFF",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x00",
45 "green": "0x00",
46 "blue": "0xCC",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0x7F",
67 "green": "0xBF",
68 "blue": "0xFF",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/StatusNeutral.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0x5A",
9 "green": "0x5A",
10 "blue": "0x5F",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0xA1",
27 "green": "0xA1",
28 "blue": "0xA6",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x3A",
45 "green": "0x3A",
46 "blue": "0x3C",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0xC7",
67 "green": "0xC7",
68 "blue": "0xCC",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/StatusPositive.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0x14",
9 "green": "0x6C",
10 "blue": "0x2E",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0x30",
27 "green": "0xD1",
28 "blue": "0x58",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x0B",
45 "green": "0x50",
46 "blue": "0x23",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0x5B",
67 "green": "0xE5",
68 "blue": "0x84",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/StatusWarning.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0x7A",
9 "green": "0x56",
10 "blue": "0x00",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0xFF",
27 "green": "0xD6",
28 "blue": "0x0A",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x5C",
45 "green": "0x41",
46 "blue": "0x00",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0xFF",
67 "green": "0xE5",
68 "blue": "0x66",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}