Commit 9e58a36915
Unsigned
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 | |||
| 33 | Each status foreground has a matching `…Surface` colour for the fill behind it, | ||
| 34 | paired through `AppStatusTone`. | ||
| 35 | |||
| 36 | ### Contrast alone is not a palette | ||
| 37 | |||
| 38 | The first version of this palette maximised contrast and produced mud. Requiring | ||
| 39 | every foreground to clear 4.5:1 against *its own 16% tint* — the harshest | ||
| 40 | surface it ever sits on — pushed each colour ~20% darker than the common case | ||
| 41 | needed. `#7A5600` is not amber, it is olive; `#146C2E` is not green so much as | ||
| 42 | bottle-dark. Contrast passed and the UI was still hard to read, because hue | ||
| 43 | identity is what tells "warning" from "critical" at a glance. | ||
| 44 | |||
| 45 | Two fixes: | ||
| 46 | |||
| 47 | 1. **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`). | ||
| 51 | 2. **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 | |||
| 56 | When adding a colour, search for the most saturated value that passes, not the | ||
| 57 | darkest. 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 | ||
| 3756 | extension ChangeImpactClassification { | 3756 | extension 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 | ||
| 538 | private func findingSeverityBadge(_ severity: AuditFindingSeverity) -> some View { | 538 | private 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. | ||
| 132 | enum 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 | |||
| 123 | struct AppStatusBadgeModel: Equatable { | 170 | struct 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 | } | ||