Commit 9e58a36915

9e58a36915bb1fe8cf88423c9519c0c12dbbf2e2

parent: 2c608ed9cb

Unsigned

cmc <hello@cleberg.net> · 2026-07-21 01:25 UTC

fix(a11y): rebalance the light palette so hues survive

Reported as "colors seem muted and hard to see on light mode", and
correct. The light palette optimised contrast and produced mud: #7A5600
reads olive rather than amber, #146C2E bottle-dark rather than green.
Contrast passed while the UI got harder to read, because hue identity is
what distinguishes warning from critical at a glance.

Two causes, both fixed.

Every foreground was required to clear 4.5:1 against its own 16% badge
tint — the harshest surface it ever sits on — which pushed each colour
about 20% darker than the common case needed. Most of what is actually on
screen is plain text on a card, with far more headroom. The fill is now
decoupled from the foreground: AppStatusTone carries a foreground and a
surface authored independently, with matching …Surface colorsets, so a
foreground no longer has to survive a wash of itself. Every status
foreground is now fully saturated.

And warning was yellow. Yellow cannot stay yellow at a lightness low
enough to pass 4.5:1 on white — it becomes olive. That is colorimetric,
not a tuning problem. Warning is now orange: #AD5100 light, #FF9F0A dark.

New light values: positive #008035, warning #AD5100, critical #CC0700.
Worst-case ratios 4.54–6.76 across page, card, and surface in both
schemes. Audit findings are unchanged — light 21, dark 18 — so the
vividness costs nothing.

Also picks up a literal .blue missed in phase 1: DomainDiffItem's
low-severity change colour, which the phase 1 sweep did not cover because
its pattern listed only cyan/yellow/green/red/orange/pink.

Layout: unified · split

Docs/ACCESSIBILITY.md +31 −5
@@ -24,11 +24,37 @@ worst of those three is shown:
24 24
25| Role | Light | Dark | Worst light | Worst dark | 25| Role | Light | Dark | Worst light | Worst dark |
26| --- | --- | --- | --- | --- | 26| --- | --- | --- | --- | --- |
27| `StatusInfo` / `AccentColor` | `#0000FF` | `#4DA3FF` | 5.50 | 5.85 | 27| `StatusInfo` / `AccentColor` | `#0000FF` | `#4DA3FF` | 6.76 | 6.47 |
28| `StatusPositive` | `#146C2E` | `#30D158` | 4.65 | 7.32 | 28| `StatusPositive` | `#008035` | `#30D158` | 4.54 | 7.62 |
29| `StatusWarning` | `#7A5600` | `#FFD60A` | 4.74 | 9.61 | 29| `StatusWarning` | `#AD5100` | `#FF9F0A` | 4.59 | 7.76 |
30| `StatusCritical` | `#B3261E` | `#FF6961` | 4.51 | 5.54 | 30| `StatusCritical` | `#CC0700` | `#FF6961` | 4.68 | 6.12 |
31| `StatusNeutral` | `#5A5A5F` | `#A1A1A6` | 4.92 | 5.88 | 31| `StatusNeutral` | `#5A5A5F` | `#A1A1A6` | 5.84 | 6.76 |
32
33Each status foreground has a matching `…Surface` colour for the fill behind it,
34paired through `AppStatusTone`.
35
36### Contrast alone is not a palette
37
38The first version of this palette maximised contrast and produced mud. Requiring
39every foreground to clear 4.5:1 against *its own 16% tint* — the harshest
40surface it ever sits on — pushed each colour ~20% darker than the common case
41needed. `#7A5600` is not amber, it is olive; `#146C2E` is not green so much as
42bottle-dark. Contrast passed and the UI was still hard to read, because hue
43identity is what tells "warning" from "critical" at a glance.
44
45Two fixes:
46
471. **Decouple the fill from the foreground.** `AppStatusTone` carries a
48 `foreground` and a `surface` that are authored independently, so the
49 foreground no longer has to survive a wash of itself. Every status foreground
50 is now fully saturated (`S = 1.0`).
512. **Warning is orange, not yellow.** Yellow cannot stay yellow at a lightness
52 low enough to clear 4.5:1 on white — it *becomes* olive. That is
53 colorimetric, not a tuning problem. Orange holds its identity when darkened,
54 so warning is `#AD5100` in light and `#FF9F0A` in dark.
55
56When adding a colour, search for the most saturated value that passes, not the
57darkest. The darkest is always easy and always wrong.
32| `AppTextSecondary` | `#5A5A5F` | `#A1A1A6` | 6.15 | 7.50 | 58| `AppTextSecondary` | `#5A5A5F` | `#A1A1A6` | 6.15 | 7.50 |
33 59
34`AppTextSecondary` replaces `.secondary` for body text. iOS's own `secondaryLabel` 60`AppTextSecondary` replaces `.secondary` for body text. iOS's own `secondaryLabel`
DomainDig/BatchResultsView.swift +8 −8
@@ -104,13 +104,13 @@ struct BatchResultRowView: View {
104 if let changeClassification = result.changeClassification { 104 if let changeClassification = result.changeClassification {
105 Text("Impact: \(changeClassification.title)") 105 Text("Impact: \(changeClassification.title)")
106 .font(appDensity.font(.caption2)) 106 .font(appDensity.font(.caption2))
107 .foregroundStyle(changeClassification.color) 107 .foregroundStyle(changeClassification.tone.foreground)
108 } 108 }
109 109
110 if let errorMessage = result.errorMessage { 110 if let errorMessage = result.errorMessage {
111 Text(errorMessage) 111 Text(errorMessage)
112 .font(appDensity.font(.caption2)) 112 .font(appDensity.font(.caption2))
113 .foregroundStyle(result.status == .failed ? Color(.statusCritical) : .secondary) 113 .foregroundStyle(result.status == .failed ? Color(.statusCritical) : Color(.appTextSecondary))
114 } 114 }
115 } 115 }
116 .frame(maxWidth: .infinity, alignment: .leading) 116 .frame(maxWidth: .infinity, alignment: .leading)
@@ -147,20 +147,20 @@ struct BatchResultRowView: View {
147 case .pending: 147 case .pending:
148 return .init(title: "Pending", systemImage: "clock", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 148 return .init(title: "Pending", systemImage: "clock", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
149 case .running: 149 case .running:
150 return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) 150 return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfoSurface))
151 case .completed: 151 case .completed:
152 if result.changeClassification == .critical || result.certificateWarningLevel == .critical || result.riskLevel == .high { 152 if result.changeClassification == .critical || result.certificateWarningLevel == .critical || result.riskLevel == .high {
153 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 153 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
154 } 154 }
155 if result.changeClassification == .warning || result.changeSeverity == .medium || result.certificateWarningLevel == .warning { 155 if result.changeClassification == .warning || result.changeSeverity == .medium || result.certificateWarningLevel == .warning {
156 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 156 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
157 } 157 }
158 if result.quickStatus == "Changed" { 158 if result.quickStatus == "Changed" {
159 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) 159 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfoSurface))
160 } 160 }
161 return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 161 return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
162 case .failed: 162 case .failed:
163 return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 163 return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
164 } 164 }
165 } 165 }
166} 166}
DomainDig/ContentView.swift +30 −30
@@ -1029,21 +1029,21 @@ struct DomainChangeSummaryView: View {
1029 HStack { 1029 HStack {
1030 Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle") 1030 Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle")
1031 .font(appDensity.font(.caption)) 1031 .font(appDensity.font(.caption))
1032 .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : Color(.statusPositive)) 1032 .foregroundStyle(summary.hasChanges ? severityTone(summary.severity).foreground : Color(.statusPositive))
1033 Spacer() 1033 Spacer()
1034 Text(summary.severity.title.uppercased()) 1034 Text(summary.severity.title.uppercased())
1035 .font(appDensity.font(.caption2)) 1035 .font(appDensity.font(.caption2))
1036 .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .secondary) 1036 .foregroundStyle(summary.hasChanges ? severityTone(summary.severity).foreground : Color(.appTextSecondary))
1037 .padding(.horizontal, 8) 1037 .padding(.horizontal, 8)
1038 .padding(.vertical, 4) 1038 .padding(.vertical, 4)
1039 .background((summary.hasChanges ? severityColor(summary.severity) : .secondary).opacity(0.16)) 1039 .background((summary.hasChanges ? severityTone(summary.severity) : AppStatusTone.neutral).surface)
1040 .clipShape(Capsule()) 1040 .clipShape(Capsule())
1041 Text(summary.impactClassification.title.uppercased()) 1041 Text(summary.impactClassification.title.uppercased())
1042 .font(appDensity.font(.caption2)) 1042 .font(appDensity.font(.caption2))
1043 .foregroundStyle(summary.impactClassification.color) 1043 .foregroundStyle(summary.impactClassification.tone.foreground)
1044 .padding(.horizontal, 8) 1044 .padding(.horizontal, 8)
1045 .padding(.vertical, 4) 1045 .padding(.vertical, 4)
1046 .background(summary.impactClassification.color.opacity(0.16)) 1046 .background(summary.impactClassification.tone.surface)
1047 .clipShape(Capsule()) 1047 .clipShape(Capsule())
1048 Text(summary.generatedAt, style: .time) 1048 Text(summary.generatedAt, style: .time)
1049 .font(appDensity.font(.caption2)) 1049 .font(appDensity.font(.caption2))
@@ -1096,14 +1096,14 @@ struct DomainChangeSummaryView: View {
1096 } 1096 }
1097 } 1097 }
1098 1098
1099 private func severityColor(_ severity: ChangeSeverity) -> Color { 1099 private func severityTone(_ severity: ChangeSeverity) -> AppStatusTone {
1100 switch severity { 1100 switch severity {
1101 case .low: 1101 case .low:
1102 return .secondary 1102 return .neutral
1103 case .medium: 1103 case .medium:
1104 return Color(.statusWarning) 1104 return .warning
1105 case .high: 1105 case .high:
1106 return Color(.statusCritical) 1106 return .critical
1107 } 1107 }
1108 } 1108 }
1109 1109
@@ -1173,10 +1173,10 @@ struct DomainDiffView: View {
1173 Spacer() 1173 Spacer()
1174 Text("\(item.changeType.marker) \(item.severity.title) • \(changeLabel(for: item.changeType))") 1174 Text("\(item.changeType.marker) \(item.severity.title) • \(changeLabel(for: item.changeType))")
1175 .font(.system(.caption2, design: .monospaced)) 1175 .font(.system(.caption2, design: .monospaced))
1176 .foregroundStyle(changeColor(for: item)) 1176 .foregroundStyle(changeTone(for: item).foreground)
1177 .padding(.horizontal, 8) 1177 .padding(.horizontal, 8)
1178 .padding(.vertical, 4) 1178 .padding(.vertical, 4)
1179 .background(changeColor(for: item).opacity(0.16)) 1179 .background(changeTone(for: item).surface)
1180 .clipShape(Capsule()) 1180 .clipShape(Capsule())
1181 } 1181 }
1182 1182
@@ -1205,7 +1205,7 @@ struct DomainDiffView: View {
1205 } 1205 }
1206 } 1206 }
1207 .padding(10) 1207 .padding(10)
1208 .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.appSurface)) 1208 .background(item.hasChanges ? changeTone(for: item).surface : Color(.appSurface))
1209 .cornerRadius(8) 1209 .cornerRadius(8)
1210 } 1210 }
1211 } label: { 1211 } label: {
@@ -1249,18 +1249,18 @@ struct DomainDiffView: View {
1249 } 1249 }
1250 } 1250 }
1251 1251
1252 private func changeColor(for item: DomainDiffItem) -> Color { 1252 private func changeTone(for item: DomainDiffItem) -> AppStatusTone {
1253 if item.changeType == .unchanged { 1253 if item.changeType == .unchanged {
1254 return .secondary 1254 return .neutral
1255 } 1255 }
1256 1256
1257 switch item.severity { 1257 switch item.severity {
1258 case .low: 1258 case .low:
1259 return .blue 1259 return .info
1260 case .medium: 1260 case .medium:
1261 return Color(.statusWarning) 1261 return .warning
1262 case .high: 1262 case .high:
1263 return Color(.statusCritical) 1263 return .critical
1264 } 1264 }
1265 } 1265 }
1266 1266
@@ -1337,7 +1337,7 @@ struct DomainSectionView: View {
1337 CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) { 1337 CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) {
1338 if let trackedDomain { 1338 if let trackedDomain {
1339 HStack(spacing: 8) { 1339 HStack(spacing: 8) {
1340 AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))) 1340 AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface)))
1341 Button { 1341 Button {
1342 onTogglePinned() 1342 onTogglePinned()
1343 } label: { 1343 } label: {
@@ -2186,9 +2186,9 @@ struct EmailSectionView: View {
2186 private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel { 2186 private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel {
2187 switch row.statusTone { 2187 switch row.statusTone {
2188 case .success: 2188 case .success:
2189 return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 2189 return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
2190 case .warning: 2190 case .warning:
2191 return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 2191 return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
2192 case .failure: 2192 case .failure:
2193 return .init(title: row.status, systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 2193 return .init(title: row.status, systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
2194 case .primary, .secondary: 2194 case .primary, .secondary:
@@ -2345,11 +2345,11 @@ struct NetworkSectionView: View {
2345 private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel { 2345 private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel {
2346 switch row.statusTone { 2346 switch row.statusTone {
2347 case .success: 2347 case .success:
2348 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 2348 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
2349 case .warning: 2349 case .warning:
2350 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 2350 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
2351 case .failure: 2351 case .failure:
2352 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 2352 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
2353 case .primary, .secondary: 2353 case .primary, .secondary:
2354 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 2354 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
2355 } 2355 }
@@ -2396,11 +2396,11 @@ struct PortRowsView: View {
2396 private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel { 2396 private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel {
2397 switch row.statusTone { 2397 switch row.statusTone {
2398 case .success: 2398 case .success:
2399 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 2399 return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
2400 case .warning: 2400 case .warning:
2401 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 2401 return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
2402 case .failure: 2402 case .failure:
2403 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 2403 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
2404 case .primary, .secondary: 2404 case .primary, .secondary:
2405 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 2405 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
2406 } 2406 }
@@ -3754,11 +3754,11 @@ private struct DataImportPreviewSheet: View {
3754} 3754}
3755 3755
3756extension ChangeImpactClassification { 3756extension ChangeImpactClassification {
3757 var color: Color { 3757 var tone: AppStatusTone {
3758 switch self { 3758 switch self {
3759 case .informational: return .secondary 3759 case .informational: return .neutral
3760 case .warning: return Color(.statusWarning) 3760 case .warning: return .warning
3761 case .critical: return Color(.statusCritical) 3761 case .critical: return .critical
3762 } 3762 }
3763 } 3763 }
3764} 3764}
DomainDig/DashboardView.swift +12 −12
@@ -237,20 +237,20 @@ struct DashboardView: View {
237 let criticalCount = states.filter { $0.health == .critical }.count 237 let criticalCount = states.filter { $0.health == .critical }.count
238 let warningCount = states.filter { $0.health == .warning }.count 238 let warningCount = states.filter { $0.health == .warning }.count
239 let title: String 239 let title: String
240 let color: Color 240 let tone: AppStatusTone
241 let systemImage: String 241 let systemImage: String
242 242
243 if criticalCount > 0 { 243 if criticalCount > 0 {
244 title = "\(criticalCount) critical" 244 title = "\(criticalCount) critical"
245 color = Color(.statusCritical) 245 tone = .critical
246 systemImage = "exclamationmark.octagon.fill" 246 systemImage = "exclamationmark.octagon.fill"
247 } else if warningCount > 0 { 247 } else if warningCount > 0 {
248 title = "\(warningCount) warning" 248 title = "\(warningCount) warning"
249 color = Color(.statusWarning) 249 tone = .warning
250 systemImage = "exclamationmark.triangle.fill" 250 systemImage = "exclamationmark.triangle.fill"
251 } else { 251 } else {
252 title = "Healthy" 252 title = "Healthy"
253 color = Color(.statusPositive) 253 tone = .positive
254 systemImage = "checkmark.circle.fill" 254 systemImage = "checkmark.circle.fill"
255 } 255 }
256 256
@@ -258,8 +258,8 @@ struct DashboardView: View {
258 model: .init( 258 model: .init(
259 title: title, 259 title: title,
260 systemImage: systemImage, 260 systemImage: systemImage,
261 foregroundColor: color, 261 foregroundColor: tone.foreground,
262 backgroundColor: color.opacity(0.16) 262 backgroundColor: tone.surface
263 ) 263 )
264 ) 264 )
265 } 265 }
@@ -331,11 +331,11 @@ private struct PortfolioAttentionRow: View {
331 private var badgeModel: AppStatusBadgeModel { 331 private var badgeModel: AppStatusBadgeModel {
332 switch item.health { 332 switch item.health {
333 case .healthy: 333 case .healthy:
334 return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 334 return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
335 case .warning: 335 case .warning:
336 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 336 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
337 case .critical: 337 case .critical:
338 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 338 return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
339 } 339 }
340 } 340 }
341} 341}
@@ -370,11 +370,11 @@ private struct PortfolioExpiryRow: View {
370 private var badgeModel: AppStatusBadgeModel { 370 private var badgeModel: AppStatusBadgeModel {
371 switch state.certificateExpiryState { 371 switch state.certificateExpiryState {
372 case .none: 372 case .none:
373 return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 373 return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
374 case .warning: 374 case .warning:
375 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 375 return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
376 case .critical: 376 case .critical:
377 return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 377 return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
378 } 378 }
379 } 379 }
380} 380}
DomainDig/DomainDig/AuditViews.swift +10 −10
@@ -526,33 +526,33 @@ private func auditStatusBadge(_ status: AuditStatus) -> some View {
526 let model: AppStatusBadgeModel 526 let model: AppStatusBadgeModel
527 switch status { 527 switch status {
528 case .draft: 528 case .draft:
529 model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 529 model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
530 case .inReview: 530 case .inReview:
531 model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) 531 model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfoSurface))
532 case .complete: 532 case .complete:
533 model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 533 model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
534 } 534 }
535 return AppStatusBadgeView(model: model) 535 return AppStatusBadgeView(model: model)
536} 536}
537 537
538private func findingSeverityBadge(_ severity: AuditFindingSeverity) -> some View { 538private func findingSeverityBadge(_ severity: AuditFindingSeverity) -> some View {
539 let color: Color 539 let tone: AppStatusTone
540 switch severity { 540 switch severity {
541 case .informational: 541 case .informational:
542 color = .secondary 542 tone = .neutral
543 case .low: 543 case .low:
544 color = Color(.statusPositive) 544 tone = .positive
545 case .medium: 545 case .medium:
546 color = Color(.statusWarning) 546 tone = .warning
547 case .high: 547 case .high:
548 color = Color(.statusCritical) 548 tone = .critical
549 } 549 }
550 return AppStatusBadgeView( 550 return AppStatusBadgeView(
551 model: .init( 551 model: .init(
552 title: severity.title, 552 title: severity.title,
553 systemImage: "exclamationmark.circle.fill", 553 systemImage: "exclamationmark.circle.fill",
554 foregroundColor: color, 554 foregroundColor: tone.foreground,
555 backgroundColor: color.opacity(0.16) 555 backgroundColor: tone.surface
556 ) 556 )
557 ) 557 )
558} 558}
DomainDig/DomainDigUI.swift +55 −8
@@ -120,6 +120,53 @@ extension EnvironmentValues {
120 } 120 }
121} 121}
122 122
123/// A status colour pairing: the foreground and the surface it sits on.
124///
125/// These travel together because they cannot be derived from one another. The
126/// badge fill used to be `foreground.opacity(0.16)`, which forced every
127/// foreground dark enough to stay legible against its own wash — that is how the
128/// light palette ended up olive-and-mud rather than amber-and-green. Decoupling
129/// them lets the foregrounds stay fully saturated.
130///
131/// See `Docs/ACCESSIBILITY.md` for the measured ratios.
132enum AppStatusTone {
133 case positive
134 case warning
135 case critical
136 case info
137 case neutral
138
139 var foreground: Color {
140 switch self {
141 case .positive:
142 return Color(.statusPositive)
143 case .warning:
144 return Color(.statusWarning)
145 case .critical:
146 return Color(.statusCritical)
147 case .info:
148 return Color(.statusInfo)
149 case .neutral:
150 return Color(.statusNeutral)
151 }
152 }
153
154 var surface: Color {
155 switch self {
156 case .positive:
157 return Color(.statusPositiveSurface)
158 case .warning:
159 return Color(.statusWarningSurface)
160 case .critical:
161 return Color(.statusCriticalSurface)
162 case .info:
163 return Color(.statusInfoSurface)
164 case .neutral:
165 return Color(.statusNeutralSurface)
166 }
167 }
168}
169
123struct AppStatusBadgeModel: Equatable { 170struct AppStatusBadgeModel: Equatable {
124 let title: String 171 let title: String
125 let systemImage: String? 172 let systemImage: String?
@@ -131,9 +178,9 @@ enum AppStatusFactory {
131 static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel { 178 static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel {
132 switch status { 179 switch status {
133 case .available: 180 case .available:
134 return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 181 return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
135 case .registered: 182 case .registered:
136 return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 183 return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
137 case .unknown, .none: 184 case .unknown, .none:
138 return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 185 return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
139 } 186 }
@@ -141,12 +188,12 @@ enum AppStatusFactory {
141 188
142 static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel { 189 static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel {
143 if error != nil || sslInfo == nil { 190 if error != nil || sslInfo == nil {
144 return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 191 return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
145 } 192 }
146 if let sslInfo, sslInfo.daysUntilExpiry <= 14 { 193 if let sslInfo, sslInfo.daysUntilExpiry <= 14 {
147 return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 194 return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
148 } 195 }
149 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 196 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
150 } 197 }
151 198
152 static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel { 199 static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel {
@@ -157,9 +204,9 @@ enum AppStatusFactory {
157 let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count 204 let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count
158 switch foundCount { 205 switch foundCount {
159 case 3: 206 case 3:
160 return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 207 return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
161 case 1, 2: 208 case 1, 2:
162 return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 209 return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
163 default: 210 default:
164 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 211 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
165 } 212 }
@@ -170,7 +217,7 @@ enum AppStatusFactory {
170 return .init(title: "Unchanged", systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 217 return .init(title: "Unchanged", systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
171 } 218 }
172 if summary.hasChanges { 219 if summary.hasChanges {
173 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) 220 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfoSurface))
174 } 221 }
175 return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) 222 return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
176 } 223 }
DomainDig/HistoryView.swift +1 −1
@@ -56,7 +56,7 @@ struct HistoryView: View {
56 title: severity.title, 56 title: severity.title,
57 systemImage: "arrow.triangle.2.circlepath", 57 systemImage: "arrow.triangle.2.circlepath",
58 foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), 58 foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning),
59 backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16) 59 backgroundColor: (severity == .high ? AppStatusTone.critical : .warning).surface
60 ) 60 )
61 ) 61 )
62 } 62 }
DomainDig/MonitoringView.swift +5 −5
@@ -78,10 +78,10 @@ struct MonitoringView: View {
78 HStack(spacing: 8) { 78 HStack(spacing: 8) {
79 metricBadge(title: "\(log.domainsChecked) checked") 79 metricBadge(title: "\(log.domainsChecked) checked")
80 if log.changesFound > 0 { 80 if log.changesFound > 0 {
81 metricBadge(title: "\(log.changesFound) changed", tint: Color(.statusWarning)) 81 metricBadge(title: "\(log.changesFound) changed", tone: .warning)
82 } 82 }
83 if log.alertsTriggered > 0 { 83 if log.alertsTriggered > 0 {
84 metricBadge(title: "\(log.alertsTriggered) alerts", tint: Color(.statusCritical)) 84 metricBadge(title: "\(log.alertsTriggered) alerts", tone: .critical)
85 } 85 }
86 } 86 }
87 } 87 }
@@ -100,13 +100,13 @@ struct MonitoringView: View {
100 } 100 }
101 } 101 }
102 102
103 private func metricBadge(title: String, tint: Color = Color(.statusInfo)) -> some View { 103 private func metricBadge(title: String, tone: AppStatusTone = .info) -> some View {
104 Text(title) 104 Text(title)
105 .font(appDensity.font(.caption2)) 105 .font(appDensity.font(.caption2))
106 .foregroundStyle(tint) 106 .foregroundStyle(tone.foreground)
107 .padding(.horizontal, 8) 107 .padding(.horizontal, 8)
108 .padding(.vertical, 4) 108 .padding(.vertical, 4)
109 .background(tint.opacity(0.16)) 109 .background(tone.surface)
110 .clipShape(Capsule()) 110 .clipShape(Capsule())
111 } 111 }
112 112
DomainDig/TimelineView.swift +1 −1
@@ -117,7 +117,7 @@ private struct TimelineRow: View {
117 title: severity.title, 117 title: severity.title,
118 systemImage: "arrow.triangle.2.circlepath", 118 systemImage: "arrow.triangle.2.circlepath",
119 foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), 119 foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning),
120 backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16) 120 backgroundColor: (severity == .high ? AppStatusTone.critical : .warning).surface
121 ) 121 )
122 ) 122 )
123 } 123 }
DomainDig/WatchlistView.swift +4 −4
@@ -516,7 +516,7 @@ struct WatchlistRowView: View {
516 title: "Shared", 516 title: "Shared",
517 systemImage: "person.2.fill", 517 systemImage: "person.2.fill",
518 foregroundColor: Color(.statusInfo), 518 foregroundColor: Color(.statusInfo),
519 backgroundColor: Color(.statusInfo).opacity(0.16) 519 backgroundColor: Color(.statusInfoSurface)
520 ) 520 )
521 ) 521 )
522 } 522 }
@@ -533,11 +533,11 @@ struct WatchlistRowView: View {
533 let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon" 533 let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon"
534 switch trackedDomain.certificateWarningLevel { 534 switch trackedDomain.certificateWarningLevel {
535 case .critical: 535 case .critical:
536 return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) 536 return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface))
537 case .warning: 537 case .warning:
538 return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) 538 return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface))
539 case .none: 539 case .none:
540 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) 540 return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))
541 } 541 }
542 } 542 }
543} 543}
Shared/Colors.xcassets/StatusCritical.colorset/Contents.json +6 −6
@@ -5,9 +5,9 @@
5 "color": { 5 "color": {
6 "color-space": "srgb", 6 "color-space": "srgb",
7 "components": { 7 "components": {
8 "red": "0xB3", 8 "red": "0xCC",
9 "green": "0x26", 9 "green": "0x07",
10 "blue": "0x1E", 10 "blue": "0x00",
11 "alpha": "1.000" 11 "alpha": "1.000"
12 } 12 }
13 } 13 }
@@ -41,9 +41,9 @@
41 "color": { 41 "color": {
42 "color-space": "srgb", 42 "color-space": "srgb",
43 "components": { 43 "components": {
44 "red": "0x8C", 44 "red": "0xA3",
45 "green": "0x1D", 45 "green": "0x05",
46 "blue": "0x16", 46 "blue": "0x00",
47 "alpha": "1.000" 47 "alpha": "1.000"
48 } 48 }
49 } 49 }
Shared/Colors.xcassets/StatusCriticalSurface.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": "0xFB",
9 "green": "0xE0",
10 "blue": "0xDF",
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": "0x2E",
27 "green": "0x13",
28 "blue": "0x10",
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": "0xF7",
45 "green": "0xCF",
46 "blue": "0xCD",
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": "0x3D",
67 "green": "0x19",
68 "blue": "0x15",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/StatusInfoSurface.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": "0xDF",
9 "green": "0xE3",
10 "blue": "0xFB",
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": "0x10",
27 "green": "0x1C",
28 "blue": "0x33",
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": "0xCB",
45 "green": "0xD2",
46 "blue": "0xF7",
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": "0x15",
67 "green": "0x27",
68 "blue": "0x44",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}
Shared/Colors.xcassets/StatusNeutralSurface.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": "0xEB",
9 "green": "0xED",
10 "blue": "0xEF",
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": "0xDF",
45 "green": "0xE1",
46 "blue": "0xE4",
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/StatusPositive.colorset/Contents.json +6 −6
@@ -5,9 +5,9 @@
5 "color": { 5 "color": {
6 "color-space": "srgb", 6 "color-space": "srgb",
7 "components": { 7 "components": {
8 "red": "0x14", 8 "red": "0x00",
9 "green": "0x6C", 9 "green": "0x80",
10 "blue": "0x2E", 10 "blue": "0x35",
11 "alpha": "1.000" 11 "alpha": "1.000"
12 } 12 }
13 } 13 }
@@ -41,9 +41,9 @@
41 "color": { 41 "color": {
42 "color-space": "srgb", 42 "color-space": "srgb",
43 "components": { 43 "components": {
44 "red": "0x0B", 44 "red": "0x00",
45 "green": "0x50", 45 "green": "0x66",
46 "blue": "0x23", 46 "blue": "0x1F",
47 "alpha": "1.000" 47 "alpha": "1.000"
48 } 48 }
49 } 49 }
Shared/Colors.xcassets/StatusPositiveSurface.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": "0xE3",
9 "green": "0xF7",
10 "blue": "0xEC",
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": "0x2A",
28 "blue": "0x18",
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": "0xD3",
45 "green": "0xF0",
46 "blue": "0xE0",
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": "0x12",
67 "green": "0x3A",
68 "blue": "0x20",
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 +7 −7
@@ -5,8 +5,8 @@
5 "color": { 5 "color": {
6 "color-space": "srgb", 6 "color-space": "srgb",
7 "components": { 7 "components": {
8 "red": "0x7A", 8 "red": "0xAD",
9 "green": "0x56", 9 "green": "0x51",
10 "blue": "0x00", 10 "blue": "0x00",
11 "alpha": "1.000" 11 "alpha": "1.000"
12 } 12 }
@@ -24,7 +24,7 @@
24 "color-space": "srgb", 24 "color-space": "srgb",
25 "components": { 25 "components": {
26 "red": "0xFF", 26 "red": "0xFF",
27 "green": "0xD6", 27 "green": "0x9F",
28 "blue": "0x0A", 28 "blue": "0x0A",
29 "alpha": "1.000" 29 "alpha": "1.000"
30 } 30 }
@@ -41,8 +41,8 @@
41 "color": { 41 "color": {
42 "color-space": "srgb", 42 "color-space": "srgb",
43 "components": { 43 "components": {
44 "red": "0x5C", 44 "red": "0x8A",
45 "green": "0x41", 45 "green": "0x3F",
46 "blue": "0x00", 46 "blue": "0x00",
47 "alpha": "1.000" 47 "alpha": "1.000"
48 } 48 }
@@ -64,8 +64,8 @@
64 "color-space": "srgb", 64 "color-space": "srgb",
65 "components": { 65 "components": {
66 "red": "0xFF", 66 "red": "0xFF",
67 "green": "0xE5", 67 "green": "0xBF",
68 "blue": "0x66", 68 "blue": "0x5C",
69 "alpha": "1.000" 69 "alpha": "1.000"
70 } 70 }
71 } 71 }
Shared/Colors.xcassets/StatusWarningSurface.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": "0xFB",
9 "green": "0xEC",
10 "blue": "0xDF",
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": "0x2E",
27 "green": "0x1F",
28 "blue": "0x08",
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": "0xF7",
45 "green": "0xE0",
46 "blue": "0xC9",
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": "0x3D",
67 "green": "0x2A",
68 "blue": "0x0B",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}