Commit eb8a0d30da
Unsigned
Layout: unified · split
Docs/ACCESSIBILITY.md +58
| @@ -6,6 +6,58 @@ large Dynamic Type, element descriptions, trait correctness, and Dynamic Type | ||
| 6 | 6 | support — the same ground the accessibility pass tracked in |
| 7 | 7 | [issue #21](https://github.com/zerolabsco/domain-dig/issues/21) covers. |
| 8 | 8 | |
| 9 | ## The colour palette | |
| 10 | ||
| 11 | Semantic colours live in `Shared/Colors.xcassets`, which is inside the `Shared` | |
| 12 | file-system-synchronized group and therefore reaches the app, the widget, and | |
| 13 | the share extension automatically. `AccentColor` stays in | |
| 14 | `DomainDig/Assets.xcassets` because it is the system-wide tint resolved via | |
| 15 | `ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME`. | |
| 16 | ||
| 17 | Use the generated asset symbols — `Color(.statusCritical)`, `Color(.appSurface)` | |
| 18 | — never a literal. `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS` | |
| 19 | is on, so these are compile-time checked; a typo will not build. | |
| 20 | ||
| 21 | Every value clears WCAG AA (4.5:1) as text on its page, on its card, **and on | |
| 22 | its own 16% badge tint** — the way `AppStatusBadgeView` actually draws it. The | |
| 23 | worst of those three is shown: | |
| 24 | ||
| 25 | | Role | Light | Dark | Worst light | Worst dark | | |
| 26 | | --- | --- | --- | --- | --- | | |
| 27 | | `StatusInfo` / `AccentColor` | `#0000FF` | `#4DA3FF` | 5.50 | 5.85 | | |
| 28 | | `StatusPositive` | `#146C2E` | `#30D158` | 4.65 | 7.32 | | |
| 29 | | `StatusWarning` | `#7A5600` | `#FFD60A` | 4.74 | 9.61 | | |
| 30 | | `StatusCritical` | `#B3261E` | `#FF6961` | 4.51 | 5.54 | | |
| 31 | | `StatusNeutral` | `#5A5A5F` | `#A1A1A6` | 4.92 | 5.88 | | |
| 32 | ||
| 33 | High Contrast variants push further in the same direction. Surfaces | |
| 34 | (`AppBackground`, `AppSurface`, `AppSurfaceElevated`, `AppSeparator`) carry no | |
| 35 | meaning, so they get Any/Dark and, where useful, High Contrast — but no status | |
| 36 | semantics. | |
| 37 | ||
| 38 | Why custom values instead of the system palette: **every** system colour fails | |
| 39 | in light mode. Measured on white — systemYellow 1.51:1, systemOrange 2.20:1, | |
| 40 | systemGreen 2.22:1, systemCyan 2.54:1, systemRed 3.55:1. All of them pass in | |
| 41 | dark mode, which is why the dark-locked app looked fine and why unlocking light | |
| 42 | mode is impossible without this work. | |
| 43 | ||
| 44 | ### The accent has two roles, and they conflict | |
| 45 | ||
| 46 | An accent used as **text on a dark background** must be light. The same accent | |
| 47 | used as a **fill behind a white label** must be dark. One value cannot do both: | |
| 48 | `#4DA3FF` reads at 8.00:1 as text on black, but only 2.63:1 behind white text. | |
| 49 | ||
| 50 | So there are two colours: | |
| 51 | ||
| 52 | - `StatusInfo` / `AccentColor` — the accent as *foreground*: text, icons, | |
| 53 | bordered-button labels, tab bar. | |
| 54 | - `AccentFill` — the accent as a *filled background* behind a label, used by | |
| 55 | `.borderedProminent`. Stays dark in both schemes so a white label clears AA | |
| 56 | (8.59:1 light, 7.56:1 dark). | |
| 57 | ||
| 58 | `AppOnAccent` is the label colour for a solid accent fill and flips by scheme — | |
| 59 | white on the light accent, black on the dark one. | |
| 60 | ||
| 9 | 61 | ## Findings are reported, not enforced |
| 10 | 62 | |
| 11 | 63 | The audit surfaces violations that exist today, so failing on all of them would |
| @@ -85,6 +137,12 @@ pre-commit that blocks every commit. A hook routinely bypassed with | ||
| 85 | 137 | |
| 86 | 138 | ## Notes |
| 87 | 139 | |
| 140 | - **Disabled controls are a false positive.** WCAG 1.4.3 exempts inactive | |
| 141 | components from contrast requirements, but the audit flags them anyway. The | |
| 142 | Inspect screen's Run button is disabled until a domain is typed, and auditing | |
| 143 | the empty state reported a contrast failure that was never a real defect — | |
| 144 | which is why `testInspectScreen` types a domain before auditing. Watch for | |
| 145 | this before "fixing" a contrast finding on a disabled control. | |
| 88 | 146 | - Audits retry up to three times. Slower machines can miss the audit's internal |
| 89 | 147 | deadline (`Audit failed to complete in time`, code `-56`), which is a tooling |
| 90 | 148 | timeout, not an app defect. A screen that still cannot be audited is reported |
DomainDig/Assets.xcassets/AccentColor.colorset/Contents.json +72 −5
| @@ -1,11 +1,78 @@ | ||
| 1 | 1 | { |
| 2 | "colors" : [ | |
| 2 | "colors": [ | |
| 3 | 3 | { |
| 4 | "idiom" : "universal" | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0x00", | |
| 9 | "green": "0x00", | |
| 10 | "blue": "0xFF", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x4D", | |
| 27 | "green": "0xA3", | |
| 28 | "blue": "0xFF", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0x00", | |
| 45 | "green": "0x00", | |
| 46 | "blue": "0xCC", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0x7F", | |
| 67 | "green": "0xBF", | |
| 68 | "blue": "0xFF", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 5 | 72 | } |
| 6 | 73 | ], |
| 7 | "info" : { | |
| 8 | "author" : "xcode", | |
| 9 | "version" : 1 | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 10 | 77 | } |
| 11 | 78 | } |
DomainDig/BatchResultsView.swift +10 −10
| @@ -13,7 +13,7 @@ struct BatchResultsView: View { | ||
| 13 | 13 | if viewModel.batchLookupRunning { |
| 14 | 14 | VStack(alignment: .trailing, spacing: 4) { |
| 15 | 15 | ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) |
| 16 | .tint(.cyan) | |
| 16 | .tint(Color(.statusInfo)) | |
| 17 | 17 | .frame(width: 120) |
| 18 | 18 | Text(viewModel.batchProgressLabel) |
| 19 | 19 | .font(appDensity.font(.caption2)) |
| @@ -110,7 +110,7 @@ struct BatchResultRowView: View { | ||
| 110 | 110 | if let errorMessage = result.errorMessage { |
| 111 | 111 | Text(errorMessage) |
| 112 | 112 | .font(appDensity.font(.caption2)) |
| 113 | .foregroundStyle(result.status == .failed ? .red : .secondary) | |
| 113 | .foregroundStyle(result.status == .failed ? Color(.statusCritical) : .secondary) | |
| 114 | 114 | } |
| 115 | 115 | } |
| 116 | 116 | .frame(maxWidth: .infinity, alignment: .leading) |
| @@ -138,29 +138,29 @@ struct BatchResultRowView: View { | ||
| 138 | 138 | title: availabilityText, |
| 139 | 139 | systemImage: "exclamationmark.circle", |
| 140 | 140 | foregroundColor: .secondary, |
| 141 | backgroundColor: Color(.systemGray5).opacity(0.55) | |
| 141 | backgroundColor: Color(.appSurfaceElevated) | |
| 142 | 142 | ) |
| 143 | 143 | } |
| 144 | 144 | |
| 145 | 145 | private var quickStatusBadge: AppStatusBadgeModel { |
| 146 | 146 | switch result.status { |
| 147 | 147 | case .pending: |
| 148 | return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 148 | return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 149 | 149 | case .running: |
| 150 | return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) | |
| 150 | return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) | |
| 151 | 151 | case .completed: |
| 152 | 152 | if result.changeClassification == .critical || result.certificateWarningLevel == .critical || result.riskLevel == .high { |
| 153 | return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 153 | return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 154 | 154 | } |
| 155 | 155 | if result.changeClassification == .warning || result.changeSeverity == .medium || result.certificateWarningLevel == .warning { |
| 156 | return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 156 | return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 157 | 157 | } |
| 158 | 158 | if result.quickStatus == "Changed" { |
| 159 | return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) | |
| 159 | return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) | |
| 160 | 160 | } |
| 161 | return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 161 | return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 162 | 162 | case .failed: |
| 163 | return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 163 | return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 164 | 164 | } |
| 165 | 165 | } |
| 166 | 166 | } |
DomainDig/ContentView.swift +79 −71
| @@ -167,7 +167,7 @@ struct ContentView: View { | ||
| 167 | 167 | } |
| 168 | 168 | .background( |
| 169 | 169 | LinearGradient( |
| 170 | colors: [Color.black, Color(.systemGray6).opacity(0.12)], | |
| 170 | colors: [Color(.appBackground), Color(.appSurface)], | |
| 171 | 171 | startPoint: .top, |
| 172 | 172 | endPoint: .bottom |
| 173 | 173 | ) |
| @@ -302,7 +302,7 @@ struct ContentView: View { | ||
| 302 | 302 | .keyboardType(.URL) |
| 303 | 303 | .padding(.horizontal, 12) |
| 304 | 304 | .padding(.vertical, appDensity.metrics.controlVerticalPadding) |
| 305 | .background(Color(.systemGray6)) | |
| 305 | .background(Color(.appSurfaceElevated)) | |
| 306 | 306 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 307 | 307 | .focused($focusedInputField, equals: .singleDomain) |
| 308 | 308 | .onSubmit { |
| @@ -320,6 +320,7 @@ struct ContentView: View { | ||
| 320 | 320 | .frame(minHeight: appDensity.metrics.controlMinHeight) |
| 321 | 321 | } |
| 322 | 322 | .buttonStyle(.borderedProminent) |
| 323 | .tint(Color(.accentFill)) | |
| 323 | 324 | .disabled(viewModel.trimmedDomain.isEmpty) |
| 324 | 325 | } else { |
| 325 | 326 | if FeatureAccessService.hasAccess(to: .batchOperations) { |
| @@ -346,7 +347,7 @@ struct ContentView: View { | ||
| 346 | 347 | .lineLimit(4...10) |
| 347 | 348 | .padding(.horizontal, 12) |
| 348 | 349 | .padding(.vertical, appDensity.metrics.controlVerticalPadding) |
| 349 | .background(Color(.systemGray6)) | |
| 350 | .background(Color(.appSurfaceElevated)) | |
| 350 | 351 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 351 | 352 | .focused($focusedInputField, equals: .bulkDomains) |
| 352 | 353 | |
| @@ -360,6 +361,7 @@ struct ContentView: View { | ||
| 360 | 361 | .frame(minHeight: appDensity.metrics.controlMinHeight) |
| 361 | 362 | } |
| 362 | 363 | .buttonStyle(.borderedProminent) |
| 364 | .tint(Color(.accentFill)) | |
| 363 | 365 | .disabled(viewModel.bulkInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.batchLookupRunning) |
| 364 | 366 | } |
| 365 | 367 | } else { |
| @@ -549,7 +551,7 @@ struct ContentView: View { | ||
| 549 | 551 | } label: { |
| 550 | 552 | Image(systemName: viewModel.isCurrentDomainSaved ? "bookmark.fill" : "bookmark") |
| 551 | 553 | .font(appDensity.font(.body, design: .default)) |
| 552 | .foregroundStyle(viewModel.isCurrentDomainSaved ? .yellow : .secondary) | |
| 554 | .foregroundStyle(viewModel.isCurrentDomainSaved ? Color(.statusWarning) : .secondary) | |
| 553 | 555 | } |
| 554 | 556 | Menu { |
| 555 | 557 | Button("Export TXT") { |
| @@ -671,7 +673,7 @@ struct ContentView: View { | ||
| 671 | 673 | .frame(maxWidth: .infinity, alignment: .leading) |
| 672 | 674 | .padding(.vertical, 8) |
| 673 | 675 | .padding(.horizontal, 10) |
| 674 | .background(Color(.systemGray6).opacity(0.5)) | |
| 676 | .background(Color(.appSurface)) | |
| 675 | 677 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 676 | 678 | } |
| 677 | 679 | } |
| @@ -696,7 +698,7 @@ struct ContentView: View { | ||
| 696 | 698 | } |
| 697 | 699 | .frame(maxWidth: .infinity, alignment: .leading) |
| 698 | 700 | .padding(appDensity.metrics.cardPadding) |
| 699 | .background(Color(.systemGray6)) | |
| 701 | .background(Color(.appSurfaceElevated)) | |
| 700 | 702 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 701 | 703 | } |
| 702 | 704 | |
| @@ -792,7 +794,7 @@ struct SummaryView: View { | ||
| 792 | 794 | .frame(minHeight: appDensity.metrics.rowMinHeight + 12, alignment: .topLeading) |
| 793 | 795 | .frame(maxWidth: .infinity, alignment: .leading) |
| 794 | 796 | .padding(appDensity.metrics.cardPadding) |
| 795 | .background(Color(.systemGray6).opacity(0.5)) | |
| 797 | .background(Color(.appSurface)) | |
| 796 | 798 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 797 | 799 | } |
| 798 | 800 | } |
| @@ -851,22 +853,22 @@ struct RiskSummaryCardView: View { | ||
| 851 | 853 | private var levelColor: Color { |
| 852 | 854 | switch report.riskAssessment.level { |
| 853 | 855 | case .low: |
| 854 | return .green | |
| 856 | return Color(.statusPositive) | |
| 855 | 857 | case .medium: |
| 856 | return .yellow | |
| 858 | return Color(.statusWarning) | |
| 857 | 859 | case .high: |
| 858 | return .red | |
| 860 | return Color(.statusCritical) | |
| 859 | 861 | } |
| 860 | 862 | } |
| 861 | 863 | |
| 862 | 864 | private func factorColor(_ impact: RiskImpact) -> Color { |
| 863 | 865 | switch impact { |
| 864 | 866 | case .positive: |
| 865 | return .green | |
| 867 | return Color(.statusPositive) | |
| 866 | 868 | case .neutral: |
| 867 | 869 | return .secondary |
| 868 | 870 | case .negative: |
| 869 | return .red | |
| 871 | return Color(.statusCritical) | |
| 870 | 872 | } |
| 871 | 873 | } |
| 872 | 874 | } |
| @@ -888,7 +890,7 @@ struct InsightsSummaryCardView: View { | ||
| 888 | 890 | HStack(alignment: .top, spacing: 8) { |
| 889 | 891 | Image(systemName: "chart.line.uptrend.xyaxis") |
| 890 | 892 | .font(appDensity.font(.caption2)) |
| 891 | .foregroundStyle(.cyan) | |
| 893 | .foregroundStyle(Color(.statusInfo)) | |
| 892 | 894 | .padding(.top, 2) |
| 893 | 895 | Text(insight) |
| 894 | 896 | .font(appDensity.font(.caption)) |
| @@ -995,13 +997,13 @@ struct LookupStatusBannerView: View { | ||
| 995 | 997 | private var color: Color { |
| 996 | 998 | switch resultSource { |
| 997 | 999 | case .live: |
| 998 | return .green | |
| 1000 | return Color(.statusPositive) | |
| 999 | 1001 | case .cached: |
| 1000 | 1002 | return .secondary |
| 1001 | 1003 | case .mixed: |
| 1002 | return .yellow | |
| 1004 | return Color(.statusWarning) | |
| 1003 | 1005 | case .snapshot: |
| 1004 | return .orange | |
| 1006 | return Color(.statusWarning) | |
| 1005 | 1007 | } |
| 1006 | 1008 | } |
| 1007 | 1009 | |
| @@ -1029,7 +1031,7 @@ struct DomainChangeSummaryView: View { | ||
| 1029 | 1031 | HStack { |
| 1030 | 1032 | Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle") |
| 1031 | 1033 | .font(appDensity.font(.caption)) |
| 1032 | .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .green) | |
| 1034 | .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : Color(.statusPositive)) | |
| 1033 | 1035 | Spacer() |
| 1034 | 1036 | Text(summary.severity.title.uppercased()) |
| 1035 | 1037 | .font(appDensity.font(.caption2)) |
| @@ -1079,13 +1081,13 @@ struct DomainChangeSummaryView: View { | ||
| 1079 | 1081 | if let riskScoreDelta = summary.riskScoreDelta { |
| 1080 | 1082 | Text("Risk delta: \(riskScoreDelta >= 0 ? "+" : "")\(riskScoreDelta)") |
| 1081 | 1083 | .font(appDensity.font(.caption2)) |
| 1082 | .foregroundStyle(riskScoreDelta > 0 ? .orange : .secondary) | |
| 1084 | .foregroundStyle(riskScoreDelta > 0 ? Color(.statusWarning) : .secondary) | |
| 1083 | 1085 | } |
| 1084 | 1086 | |
| 1085 | 1087 | if let contextNote = summary.contextNote { |
| 1086 | 1088 | Text(contextNote) |
| 1087 | 1089 | .font(appDensity.font(.caption2)) |
| 1088 | .foregroundStyle(.orange) | |
| 1090 | .foregroundStyle(Color(.statusWarning)) | |
| 1089 | 1091 | } |
| 1090 | 1092 | } |
| 1091 | 1093 | .padding(.top, 4) |
| @@ -1101,9 +1103,9 @@ struct DomainChangeSummaryView: View { | ||
| 1101 | 1103 | case .low: |
| 1102 | 1104 | return .secondary |
| 1103 | 1105 | case .medium: |
| 1104 | return .yellow | |
| 1106 | return Color(.statusWarning) | |
| 1105 | 1107 | case .high: |
| 1106 | return .red | |
| 1108 | return Color(.statusCritical) | |
| 1107 | 1109 | } |
| 1108 | 1110 | } |
| 1109 | 1111 | |
| @@ -1205,7 +1207,7 @@ struct DomainDiffView: View { | ||
| 1205 | 1207 | } |
| 1206 | 1208 | } |
| 1207 | 1209 | .padding(10) |
| 1208 | .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.systemGray6).opacity(0.25)) | |
| 1210 | .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.appSurface)) | |
| 1209 | 1211 | .cornerRadius(8) |
| 1210 | 1212 | } |
| 1211 | 1213 | } label: { |
| @@ -1225,7 +1227,7 @@ struct DomainDiffView: View { | ||
| 1225 | 1227 | .overlay { |
| 1226 | 1228 | if highlightedSectionID == section.id { |
| 1227 | 1229 | RoundedRectangle(cornerRadius: 12) |
| 1228 | .stroke(Color.cyan.opacity(0.55), lineWidth: 1) | |
| 1230 | .stroke(Color(.statusInfo).opacity(0.55), lineWidth: 1) | |
| 1229 | 1231 | } |
| 1230 | 1232 | } |
| 1231 | 1233 | } |
| @@ -1258,9 +1260,9 @@ struct DomainDiffView: View { | ||
| 1258 | 1260 | case .low: |
| 1259 | 1261 | return .blue |
| 1260 | 1262 | case .medium: |
| 1261 | return .yellow | |
| 1263 | return Color(.statusWarning) | |
| 1262 | 1264 | case .high: |
| 1263 | return .red | |
| 1265 | return Color(.statusCritical) | |
| 1264 | 1266 | } |
| 1265 | 1267 | } |
| 1266 | 1268 | |
| @@ -1269,9 +1271,9 @@ struct DomainDiffView: View { | ||
| 1269 | 1271 | case .low: |
| 1270 | 1272 | return .blue |
| 1271 | 1273 | case .medium: |
| 1272 | return .yellow | |
| 1274 | return Color(.statusWarning) | |
| 1273 | 1275 | case .high: |
| 1274 | return .red | |
| 1276 | return Color(.statusCritical) | |
| 1275 | 1277 | } |
| 1276 | 1278 | } |
| 1277 | 1279 | |
| @@ -1337,7 +1339,7 @@ struct DomainSectionView: View { | ||
| 1337 | 1339 | CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) { |
| 1338 | 1340 | if let trackedDomain { |
| 1339 | 1341 | HStack(spacing: 8) { |
| 1340 | AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))) | |
| 1342 | AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))) | |
| 1341 | 1343 | Button { |
| 1342 | 1344 | onTogglePinned() |
| 1343 | 1345 | } label: { |
| @@ -1638,7 +1640,7 @@ struct IntelligenceSectionView: View { | ||
| 1638 | 1640 | if item.isEphemeral { |
| 1639 | 1641 | Text("Ephemeral") |
| 1640 | 1642 | .font(appDensity.font(.caption2)) |
| 1641 | .foregroundStyle(.yellow) | |
| 1643 | .foregroundStyle(Color(.statusWarning)) | |
| 1642 | 1644 | } |
| 1643 | 1645 | } |
| 1644 | 1646 | Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x") |
| @@ -1667,7 +1669,7 @@ struct IntelligenceSectionView: View { | ||
| 1667 | 1669 | VStack(alignment: .leading, spacing: 8) { |
| 1668 | 1670 | Text(title) |
| 1669 | 1671 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 1670 | .foregroundStyle(.cyan) | |
| 1672 | .foregroundStyle(Color(.statusInfo)) | |
| 1671 | 1673 | content() |
| 1672 | 1674 | } |
| 1673 | 1675 | } |
| @@ -1758,7 +1760,7 @@ struct SubdomainsSectionView: View { | ||
| 1758 | 1760 | HStack { |
| 1759 | 1761 | Text("\(group.label).*") |
| 1760 | 1762 | .font(appDensity.font(.caption)) |
| 1761 | .foregroundStyle(.cyan) | |
| 1763 | .foregroundStyle(Color(.statusInfo)) | |
| 1762 | 1764 | Spacer() |
| 1763 | 1765 | Text("\(group.subdomains.count)") |
| 1764 | 1766 | .font(appDensity.font(.caption2)) |
| @@ -1776,10 +1778,10 @@ struct SubdomainsSectionView: View { | ||
| 1776 | 1778 | if row.isInteresting { |
| 1777 | 1779 | Text("Interesting") |
| 1778 | 1780 | .font(.system(.caption2, design: .monospaced)) |
| 1779 | .foregroundStyle(.yellow) | |
| 1781 | .foregroundStyle(Color(.statusWarning)) | |
| 1780 | 1782 | .padding(.horizontal, 8) |
| 1781 | 1783 | .padding(.vertical, 4) |
| 1782 | .background(Color.yellow.opacity(0.14)) | |
| 1784 | .background(Color(.statusWarning).opacity(0.14)) | |
| 1783 | 1785 | .clipShape(Capsule()) |
| 1784 | 1786 | } |
| 1785 | 1787 | } |
| @@ -1883,7 +1885,7 @@ struct DNSSectionView: View { | ||
| 1883 | 1885 | Text(section.title) |
| 1884 | 1886 | .font(.system(.subheadline, design: .monospaced)) |
| 1885 | 1887 | .fontWeight(.semibold) |
| 1886 | .foregroundStyle(.cyan) | |
| 1888 | .foregroundStyle(Color(.statusInfo)) | |
| 1887 | 1889 | |
| 1888 | 1890 | if let message = section.message { |
| 1889 | 1891 | MessageRowView(text: message.text, isError: message.isError) |
| @@ -1910,7 +1912,7 @@ struct DNSSectionView: View { | ||
| 1910 | 1912 | Text("PTR") |
| 1911 | 1913 | .font(.system(.subheadline, design: .monospaced)) |
| 1912 | 1914 | .fontWeight(.semibold) |
| 1913 | .foregroundStyle(.cyan) | |
| 1915 | .foregroundStyle(Color(.statusInfo)) | |
| 1914 | 1916 | SectionTrustMetadataView(provenance: ptrProvenance, confidence: nil) |
| 1915 | 1917 | MessageRowView(text: ptrMessage.text, isError: ptrMessage.isError) |
| 1916 | 1918 | } |
| @@ -1920,7 +1922,7 @@ struct DNSSectionView: View { | ||
| 1920 | 1922 | HStack { |
| 1921 | 1923 | Text("History") |
| 1922 | 1924 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 1923 | .foregroundStyle(.cyan) | |
| 1925 | .foregroundStyle(Color(.statusInfo)) | |
| 1924 | 1926 | Spacer() |
| 1925 | 1927 | if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder { |
| 1926 | 1928 | Button("Load") { |
| @@ -1990,7 +1992,7 @@ struct WebSectionView: View { | ||
| 1990 | 1992 | HStack { |
| 1991 | 1993 | Text("TLS") |
| 1992 | 1994 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 1993 | .foregroundStyle(.cyan) | |
| 1995 | .foregroundStyle(Color(.statusInfo)) | |
| 1994 | 1996 | Spacer() |
| 1995 | 1997 | if !sslLoading { |
| 1996 | 1998 | AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: sslInfo, error: sslError)) |
| @@ -2034,7 +2036,7 @@ struct WebSectionView: View { | ||
| 2034 | 2036 | CardView { |
| 2035 | 2037 | Text("Headers") |
| 2036 | 2038 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2037 | .foregroundStyle(.cyan) | |
| 2039 | .foregroundStyle(Color(.statusInfo)) | |
| 2038 | 2040 | SectionTrustMetadataView(provenance: httpProvenance, confidence: nil) |
| 2039 | 2041 | if headersLoading { |
| 2040 | 2042 | ProgressView("Fetching headers…") |
| @@ -2052,7 +2054,7 @@ struct WebSectionView: View { | ||
| 2052 | 2054 | HStack(alignment: .top, spacing: 4) { |
| 2053 | 2055 | Text(header.name + ":") |
| 2054 | 2056 | .font(appDensity.font(.caption)) |
| 2055 | .foregroundStyle(header.isSecurityHeader ? .yellow : .cyan) | |
| 2057 | .foregroundStyle(header.isSecurityHeader ? Color(.statusWarning) : Color(.statusInfo)) | |
| 2056 | 2058 | Text(header.value) |
| 2057 | 2059 | .font(appDensity.font(.caption)) |
| 2058 | 2060 | .foregroundStyle(.primary) |
| @@ -2067,7 +2069,7 @@ struct WebSectionView: View { | ||
| 2067 | 2069 | HStack { |
| 2068 | 2070 | Text("Redirects") |
| 2069 | 2071 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2070 | .foregroundStyle(.cyan) | |
| 2072 | .foregroundStyle(Color(.statusInfo)) | |
| 2071 | 2073 | Spacer() |
| 2072 | 2074 | if let finalURL { |
| 2073 | 2075 | AppCopyButton(value: finalURL, label: "Copy redirect URL") |
| @@ -2093,7 +2095,7 @@ struct WebSectionView: View { | ||
| 2093 | 2095 | .frame(width: 16, alignment: .trailing) |
| 2094 | 2096 | Text(redirect.statusCode) |
| 2095 | 2097 | .font(appDensity.font(.caption)) |
| 2096 | .foregroundStyle(.cyan) | |
| 2098 | .foregroundStyle(Color(.statusInfo)) | |
| 2097 | 2099 | .frame(width: 36, alignment: .leading) |
| 2098 | 2100 | Text(redirect.url) |
| 2099 | 2101 | .font(appDensity.font(.caption)) |
| @@ -2163,7 +2165,7 @@ struct EmailSectionView: View { | ||
| 2163 | 2165 | HStack(spacing: 8) { |
| 2164 | 2166 | Text(row.label) |
| 2165 | 2167 | .font(appDensity.font(.caption)) |
| 2166 | .foregroundStyle(.cyan) | |
| 2168 | .foregroundStyle(Color(.statusInfo)) | |
| 2167 | 2169 | .frame(width: 76, alignment: .leading) |
| 2168 | 2170 | AppStatusBadgeView(model: emailRowBadge(row)) |
| 2169 | 2171 | } |
| @@ -2186,13 +2188,13 @@ struct EmailSectionView: View { | ||
| 2186 | 2188 | private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel { |
| 2187 | 2189 | switch row.statusTone { |
| 2188 | 2190 | case .success: |
| 2189 | return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 2191 | return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 2190 | 2192 | case .warning: |
| 2191 | return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 2193 | return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 2192 | 2194 | case .failure: |
| 2193 | return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 2195 | return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 2194 | 2196 | case .primary, .secondary: |
| 2195 | return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 2197 | return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 2196 | 2198 | } |
| 2197 | 2199 | } |
| 2198 | 2200 | } |
| @@ -2227,7 +2229,7 @@ struct NetworkSectionView: View { | ||
| 2227 | 2229 | CardView { |
| 2228 | 2230 | Text("Reachability") |
| 2229 | 2231 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2230 | .foregroundStyle(.cyan) | |
| 2232 | .foregroundStyle(Color(.statusInfo)) | |
| 2231 | 2233 | SectionTrustMetadataView(provenance: reachabilityProvenance, confidence: nil) |
| 2232 | 2234 | if reachabilityLoading { |
| 2233 | 2235 | ProgressView("Checking ports…") |
| @@ -2252,7 +2254,7 @@ struct NetworkSectionView: View { | ||
| 2252 | 2254 | CardView(allowsHorizontalScroll: false) { |
| 2253 | 2255 | Text("Location") |
| 2254 | 2256 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2255 | .foregroundStyle(.cyan) | |
| 2257 | .foregroundStyle(Color(.statusInfo)) | |
| 2256 | 2258 | SectionTrustMetadataView(provenance: geolocationProvenance, confidence: geolocationConfidence) |
| 2257 | 2259 | if geolocationLoading { |
| 2258 | 2260 | ProgressView("Looking up location…") |
| @@ -2284,13 +2286,13 @@ struct NetworkSectionView: View { | ||
| 2284 | 2286 | CardView(allowsHorizontalScroll: false) { |
| 2285 | 2287 | Text("Port Scan") |
| 2286 | 2288 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2287 | .foregroundStyle(.cyan) | |
| 2289 | .foregroundStyle(Color(.statusInfo)) | |
| 2288 | 2290 | SectionTrustMetadataView(provenance: portScanProvenance, confidence: nil) |
| 2289 | 2291 | |
| 2290 | 2292 | if isCloudflareProxied { |
| 2291 | 2293 | Text("Domain is behind Cloudflare's proxy. Results reflect the edge, not the origin.") |
| 2292 | 2294 | .font(appDensity.font(.caption2)) |
| 2293 | .foregroundStyle(.orange) | |
| 2295 | .foregroundStyle(Color(.statusWarning)) | |
| 2294 | 2296 | .fixedSize(horizontal: false, vertical: true) |
| 2295 | 2297 | } |
| 2296 | 2298 | |
| @@ -2314,7 +2316,7 @@ struct NetworkSectionView: View { | ||
| 2314 | 2316 | .autocorrectionDisabled() |
| 2315 | 2317 | .keyboardType(.numberPad) |
| 2316 | 2318 | .padding(10) |
| 2317 | .background(Color(.systemGray6).opacity(0.5)) | |
| 2319 | .background(Color(.appSurface)) | |
| 2318 | 2320 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 2319 | 2321 | |
| 2320 | 2322 | Button("Scan") { |
| @@ -2322,6 +2324,7 @@ struct NetworkSectionView: View { | ||
| 2322 | 2324 | onScanCustomPorts() |
| 2323 | 2325 | } |
| 2324 | 2326 | .buttonStyle(.borderedProminent) |
| 2327 | .tint(Color(.accentFill)) | |
| 2325 | 2328 | .disabled(customPortScanLoading) |
| 2326 | 2329 | |
| 2327 | 2330 | if customPortScanLoading { |
| @@ -2344,13 +2347,13 @@ struct NetworkSectionView: View { | ||
| 2344 | 2347 | private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel { |
| 2345 | 2348 | switch row.statusTone { |
| 2346 | 2349 | case .success: |
| 2347 | return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 2350 | return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 2348 | 2351 | case .warning: |
| 2349 | return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 2352 | return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 2350 | 2353 | case .failure: |
| 2351 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 2354 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 2352 | 2355 | case .primary, .secondary: |
| 2353 | return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 2356 | return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 2354 | 2357 | } |
| 2355 | 2358 | } |
| 2356 | 2359 | } |
| @@ -2395,13 +2398,13 @@ struct PortRowsView: View { | ||
| 2395 | 2398 | private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel { |
| 2396 | 2399 | switch row.statusTone { |
| 2397 | 2400 | case .success: |
| 2398 | return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 2401 | return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 2399 | 2402 | case .warning: |
| 2400 | return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 2403 | return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 2401 | 2404 | case .failure: |
| 2402 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 2405 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 2403 | 2406 | case .primary, .secondary: |
| 2404 | return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 2407 | return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 2405 | 2408 | } |
| 2406 | 2409 | } |
| 2407 | 2410 | } |
| @@ -2413,7 +2416,7 @@ struct SectionTitleView: View { | ||
| 2413 | 2416 | var body: some View { |
| 2414 | 2417 | Text(title) |
| 2415 | 2418 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) |
| 2416 | .foregroundStyle(.white) | |
| 2419 | .foregroundStyle(.primary) | |
| 2417 | 2420 | } |
| 2418 | 2421 | } |
| 2419 | 2422 | |
| @@ -2442,7 +2445,7 @@ struct CardView<Content: View>: View { | ||
| 2442 | 2445 | } |
| 2443 | 2446 | .frame(maxWidth: .infinity, alignment: .leading) |
| 2444 | 2447 | .padding(appDensity.metrics.cardPadding) |
| 2445 | .background(Color(.systemGray6).opacity(0.5)) | |
| 2448 | .background(Color(.appSurface)) | |
| 2446 | 2449 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 2447 | 2450 | } |
| 2448 | 2451 | |
| @@ -2484,7 +2487,7 @@ struct MessageRowView: View { | ||
| 2484 | 2487 | var body: some View { |
| 2485 | 2488 | Label(text, systemImage: isError ? "exclamationmark.triangle.fill" : "info.circle") |
| 2486 | 2489 | .font(appDensity.font(.caption)) |
| 2487 | .foregroundStyle(isError ? .red : .secondary) | |
| 2490 | .foregroundStyle(isError ? Color(.statusCritical) : .secondary) | |
| 2488 | 2491 | .lineLimit(nil) |
| 2489 | 2492 | .fixedSize(horizontal: false, vertical: true) |
| 2490 | 2493 | } |
| @@ -2576,6 +2579,11 @@ struct LabeledValueRow: View { | ||
| 2576 | 2579 | } |
| 2577 | 2580 | } |
| 2578 | 2581 | |
| 2582 | /// Maps a row's semantic tone onto the app palette. | |
| 2583 | /// | |
| 2584 | /// See `Docs/ACCESSIBILITY.md` for the measured contrast ratios behind these | |
| 2585 | /// colours. Never reach for a literal (`Color(.statusCritical)`, `Color(.statusWarning)`, …) — the system | |
| 2586 | /// palette fails WCAG AA badly in light mode (systemYellow is 1.28:1 on white). | |
| 2579 | 2587 | enum ResultColors { |
| 2580 | 2588 | static func color(for tone: ResultTone) -> Color { |
| 2581 | 2589 | switch tone { |
| @@ -2584,11 +2592,11 @@ enum ResultColors { | ||
| 2584 | 2592 | case .secondary: |
| 2585 | 2593 | return .secondary |
| 2586 | 2594 | case .success: |
| 2587 | return .green | |
| 2595 | return Color(.statusPositive) | |
| 2588 | 2596 | case .warning: |
| 2589 | return .yellow | |
| 2597 | return Color(.statusWarning) | |
| 2590 | 2598 | case .failure: |
| 2591 | return .red | |
| 2599 | return Color(.statusCritical) | |
| 2592 | 2600 | } |
| 2593 | 2601 | } |
| 2594 | 2602 | } |
| @@ -2654,7 +2662,7 @@ struct SettingsView: View { | ||
| 2654 | 2662 | if let errorMessage = purchaseService.errorMessage { |
| 2655 | 2663 | Text(errorMessage) |
| 2656 | 2664 | .font(appDensity.font(.caption, design: .default)) |
| 2657 | .foregroundStyle(.red) | |
| 2665 | .foregroundStyle(Color(.statusCritical)) | |
| 2658 | 2666 | } |
| 2659 | 2667 | } |
| 2660 | 2668 | |
| @@ -2789,7 +2797,7 @@ private struct HistoryNetworkSettingsView: View { | ||
| 2789 | 2797 | if let customResolverError { |
| 2790 | 2798 | Text(customResolverError) |
| 2791 | 2799 | .font(appDensity.font(.caption, design: .default)) |
| 2792 | .foregroundStyle(.red) | |
| 2800 | .foregroundStyle(Color(.statusCritical)) | |
| 2793 | 2801 | } |
| 2794 | 2802 | } |
| 2795 | 2803 | } |
| @@ -2871,7 +2879,7 @@ private struct CloudSyncSettingsView: View { | ||
| 2871 | 2879 | if let lastErrorMessage = cloudSyncService.lastErrorMessage { |
| 2872 | 2880 | Text(lastErrorMessage) |
| 2873 | 2881 | .font(appDensity.font(.caption, design: .default)) |
| 2874 | .foregroundStyle(.red) | |
| 2882 | .foregroundStyle(Color(.statusCritical)) | |
| 2875 | 2883 | } |
| 2876 | 2884 | } |
| 2877 | 2885 | } |
| @@ -2964,7 +2972,7 @@ private struct LocalAPISettingsView: View { | ||
| 2964 | 2972 | Spacer() |
| 2965 | 2973 | Text("\(log.statusCode)") |
| 2966 | 2974 | .font(appDensity.font(.caption, design: .default)) |
| 2967 | .foregroundStyle(log.statusCode >= 400 ? .red : .secondary) | |
| 2975 | .foregroundStyle(log.statusCode >= 400 ? Color(.statusCritical) : .secondary) | |
| 2968 | 2976 | } |
| 2969 | 2977 | |
| 2970 | 2978 | Text(log.timestamp.formatted(date: .abbreviated, time: .standard)) |
| @@ -3744,8 +3752,8 @@ extension ChangeImpactClassification { | ||
| 3744 | 3752 | var color: Color { |
| 3745 | 3753 | switch self { |
| 3746 | 3754 | case .informational: return .secondary |
| 3747 | case .warning: return .yellow | |
| 3748 | case .critical: return .red | |
| 3755 | case .warning: return Color(.statusWarning) | |
| 3756 | case .critical: return Color(.statusCritical) | |
| 3749 | 3757 | } |
| 3750 | 3758 | } |
| 3751 | 3759 | } |
DomainDig/DashboardView.swift +31 −31
| @@ -22,20 +22,20 @@ struct DashboardView: View { | ||
| 22 | 22 | showsCardBackground: false |
| 23 | 23 | ) |
| 24 | 24 | } |
| 25 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 25 | .listRowBackground(Color(.appSurface)) | |
| 26 | 26 | } else { |
| 27 | 27 | Section { |
| 28 | 28 | LazyVGrid(columns: summaryColumns, spacing: 10) { |
| 29 | summaryCard(title: "Total Domains", value: viewModel.portfolioDashboardData.snapshot.totalDomains, filter: .all, tint: .cyan) | |
| 30 | summaryCard(title: "Healthy", value: viewModel.portfolioDashboardData.snapshot.healthyCount, filter: .healthy, tint: .green) | |
| 31 | summaryCard(title: "Warning", value: viewModel.portfolioDashboardData.snapshot.warningCount, filter: .warning, tint: .yellow) | |
| 32 | summaryCard(title: "Critical", value: viewModel.portfolioDashboardData.snapshot.criticalCount, filter: .critical, tint: .red) | |
| 33 | summaryCard(title: "Changes (24h)", value: viewModel.portfolioDashboardData.snapshot.changedLast24h, filter: .changed, tint: .orange) | |
| 34 | summaryCard(title: "Unreachable", value: viewModel.portfolioDashboardData.snapshot.unreachableCount, filter: .unreachable, tint: .pink) | |
| 29 | summaryCard(title: "Total Domains", value: viewModel.portfolioDashboardData.snapshot.totalDomains, filter: .all, tint: Color(.statusInfo)) | |
| 30 | summaryCard(title: "Healthy", value: viewModel.portfolioDashboardData.snapshot.healthyCount, filter: .healthy, tint: Color(.statusPositive)) | |
| 31 | summaryCard(title: "Warning", value: viewModel.portfolioDashboardData.snapshot.warningCount, filter: .warning, tint: Color(.statusWarning)) | |
| 32 | summaryCard(title: "Critical", value: viewModel.portfolioDashboardData.snapshot.criticalCount, filter: .critical, tint: Color(.statusCritical)) | |
| 33 | summaryCard(title: "Changes (24h)", value: viewModel.portfolioDashboardData.snapshot.changedLast24h, filter: .changed, tint: Color(.statusWarning)) | |
| 34 | summaryCard(title: "Unreachable", value: viewModel.portfolioDashboardData.snapshot.unreachableCount, filter: .unreachable, tint: Color(.statusCritical)) | |
| 35 | 35 | } |
| 36 | 36 | .padding(.vertical, 4) |
| 37 | 37 | } |
| 38 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 38 | .listRowBackground(Color(.appSurface)) | |
| 39 | 39 | |
| 40 | 40 | Section("Quick Filters") { |
| 41 | 41 | ScrollView(.horizontal, showsIndicators: false) { |
| @@ -46,10 +46,10 @@ struct DashboardView: View { | ||
| 46 | 46 | } label: { |
| 47 | 47 | Text(filter.title) |
| 48 | 48 | .font(appDensity.font(.caption, weight: .semibold)) |
| 49 | .foregroundStyle(viewModel.dashboardFilter == filter ? Color.black : Color.white) | |
| 49 | .foregroundStyle(viewModel.dashboardFilter == filter ? Color(.appOnAccent) : Color.primary) | |
| 50 | 50 | .padding(.horizontal, 12) |
| 51 | 51 | .padding(.vertical, 8) |
| 52 | .background(viewModel.dashboardFilter == filter ? Color.cyan : Color(.systemGray5).opacity(0.6)) | |
| 52 | .background(viewModel.dashboardFilter == filter ? Color(.statusInfo) : Color(.appSurfaceElevated)) | |
| 53 | 53 | .clipShape(Capsule()) |
| 54 | 54 | } |
| 55 | 55 | .buttonStyle(.plain) |
| @@ -58,7 +58,7 @@ struct DashboardView: View { | ||
| 58 | 58 | .padding(.vertical, 4) |
| 59 | 59 | } |
| 60 | 60 | } |
| 61 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 61 | .listRowBackground(Color(.appSurface)) | |
| 62 | 62 | |
| 63 | 63 | Section("Recent Activity") { |
| 64 | 64 | if viewModel.filteredPortfolioRecentActivity.isEmpty { |
| @@ -76,7 +76,7 @@ struct DashboardView: View { | ||
| 76 | 76 | } |
| 77 | 77 | } |
| 78 | 78 | } |
| 79 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 79 | .listRowBackground(Color(.appSurface)) | |
| 80 | 80 | |
| 81 | 81 | Section("Attention Required") { |
| 82 | 82 | if viewModel.filteredPortfolioAttentionRequired.isEmpty { |
| @@ -94,7 +94,7 @@ struct DashboardView: View { | ||
| 94 | 94 | } |
| 95 | 95 | } |
| 96 | 96 | } |
| 97 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 97 | .listRowBackground(Color(.appSurface)) | |
| 98 | 98 | |
| 99 | 99 | Section("Expiring Soon") { |
| 100 | 100 | if viewModel.filteredPortfolioExpiringSoon.isEmpty { |
| @@ -110,7 +110,7 @@ struct DashboardView: View { | ||
| 110 | 110 | } |
| 111 | 111 | } |
| 112 | 112 | } |
| 113 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 113 | .listRowBackground(Color(.appSurface)) | |
| 114 | 114 | |
| 115 | 115 | Section("Portfolio List") { |
| 116 | 116 | if viewModel.filteredPortfolioGroups.isEmpty { |
| @@ -150,11 +150,11 @@ struct DashboardView: View { | ||
| 150 | 150 | } |
| 151 | 151 | } |
| 152 | 152 | } |
| 153 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 153 | .listRowBackground(Color(.appSurface)) | |
| 154 | 154 | } |
| 155 | 155 | } |
| 156 | 156 | .scrollContentBackground(.hidden) |
| 157 | .background(Color.black) | |
| 157 | .background(Color(.appBackground)) | |
| 158 | 158 | .navigationTitle("Dashboard") |
| 159 | 159 | .searchable(text: $viewModel.dashboardSearchText, prompt: "Search portfolio") |
| 160 | 160 | .toolbar { |
| @@ -196,7 +196,7 @@ struct DashboardView: View { | ||
| 196 | 196 | .foregroundStyle(.secondary) |
| 197 | 197 | Text("\(value)") |
| 198 | 198 | .font(.system(size: 28, weight: .bold, design: .rounded)) |
| 199 | .foregroundStyle(.white) | |
| 199 | .foregroundStyle(.primary) | |
| 200 | 200 | HStack { |
| 201 | 201 | Circle() |
| 202 | 202 | .fill(tint) |
| @@ -218,13 +218,13 @@ struct DashboardView: View { | ||
| 218 | 218 | if viewModel.dashboardFilter == filter { |
| 219 | 219 | return AnyShapeStyle( |
| 220 | 220 | LinearGradient( |
| 221 | colors: [Color.cyan.opacity(0.28), Color.cyan.opacity(0.12)], | |
| 221 | colors: [Color(.statusInfo).opacity(0.28), Color(.statusInfo).opacity(0.12)], | |
| 222 | 222 | startPoint: .topLeading, |
| 223 | 223 | endPoint: .bottomTrailing |
| 224 | 224 | ) |
| 225 | 225 | ) |
| 226 | 226 | } |
| 227 | return AnyShapeStyle(Color(.systemGray5).opacity(0.45)) | |
| 227 | return AnyShapeStyle(Color(.appSurfaceElevated)) | |
| 228 | 228 | } |
| 229 | 229 | |
| 230 | 230 | private func dashboardEmptyRow(_ message: String) -> some View { |
| @@ -243,15 +243,15 @@ struct DashboardView: View { | ||
| 243 | 243 | |
| 244 | 244 | if criticalCount > 0 { |
| 245 | 245 | title = "\(criticalCount) critical" |
| 246 | color = .red | |
| 246 | color = Color(.statusCritical) | |
| 247 | 247 | systemImage = "exclamationmark.octagon.fill" |
| 248 | 248 | } else if warningCount > 0 { |
| 249 | 249 | title = "\(warningCount) warning" |
| 250 | color = .yellow | |
| 250 | color = Color(.statusWarning) | |
| 251 | 251 | systemImage = "exclamationmark.triangle.fill" |
| 252 | 252 | } else { |
| 253 | 253 | title = "Healthy" |
| 254 | color = .green | |
| 254 | color = Color(.statusPositive) | |
| 255 | 255 | systemImage = "checkmark.circle.fill" |
| 256 | 256 | } |
| 257 | 257 | |
| @@ -297,11 +297,11 @@ private struct PortfolioActivityRow: View { | ||
| 297 | 297 | private var iconColor: Color { |
| 298 | 298 | switch item.health { |
| 299 | 299 | case .healthy: |
| 300 | return .cyan | |
| 300 | return Color(.statusInfo) | |
| 301 | 301 | case .warning: |
| 302 | return .yellow | |
| 302 | return Color(.statusWarning) | |
| 303 | 303 | case .critical: |
| 304 | return .red | |
| 304 | return Color(.statusCritical) | |
| 305 | 305 | } |
| 306 | 306 | } |
| 307 | 307 | } |
| @@ -332,11 +332,11 @@ private struct PortfolioAttentionRow: View { | ||
| 332 | 332 | private var badgeModel: AppStatusBadgeModel { |
| 333 | 333 | switch item.health { |
| 334 | 334 | case .healthy: |
| 335 | return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 335 | return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 336 | 336 | case .warning: |
| 337 | return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 337 | return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 338 | 338 | case .critical: |
| 339 | return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 339 | return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 340 | 340 | } |
| 341 | 341 | } |
| 342 | 342 | } |
| @@ -371,11 +371,11 @@ private struct PortfolioExpiryRow: View { | ||
| 371 | 371 | private var badgeModel: AppStatusBadgeModel { |
| 372 | 372 | switch state.certificateExpiryState { |
| 373 | 373 | case .none: |
| 374 | return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 374 | return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 375 | 375 | case .warning: |
| 376 | return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 376 | return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 377 | 377 | case .critical: |
| 378 | return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 378 | return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 379 | 379 | } |
| 380 | 380 | } |
| 381 | 381 | } |
DomainDig/DomainCompareView.swift +2 −2
| @@ -63,11 +63,11 @@ struct DomainCompareView: View { | ||
| 63 | 63 | showsCardBackground: false |
| 64 | 64 | ) |
| 65 | 65 | } |
| 66 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 66 | .listRowBackground(Color(.appSurface)) | |
| 67 | 67 | } |
| 68 | 68 | } |
| 69 | 69 | .scrollContentBackground(.hidden) |
| 70 | .background(Color.black) | |
| 70 | .background(Color(.appBackground)) | |
| 71 | 71 | .navigationTitle("Compare Domains") |
| 72 | 72 | } |
| 73 | 73 | |
DomainDig/DomainDig/AuditViews.swift +24 −24
| @@ -27,7 +27,7 @@ struct AuditListView: View { | ||
| 27 | 27 | showsCardBackground: false |
| 28 | 28 | ) |
| 29 | 29 | } |
| 30 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 30 | .listRowBackground(Color(.appSurface)) | |
| 31 | 31 | } else { |
| 32 | 32 | Section("Audit Domains") { |
| 33 | 33 | ForEach(groupedSessions, id: \.domain) { group in |
| @@ -65,11 +65,11 @@ struct AuditListView: View { | ||
| 65 | 65 | } |
| 66 | 66 | } |
| 67 | 67 | } |
| 68 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 68 | .listRowBackground(Color(.appSurface)) | |
| 69 | 69 | } |
| 70 | 70 | } |
| 71 | 71 | .scrollContentBackground(.hidden) |
| 72 | .background(Color.black) | |
| 72 | .background(Color(.appBackground)) | |
| 73 | 73 | .navigationTitle("Audit Mode") |
| 74 | 74 | .toolbar { |
| 75 | 75 | if !viewModel.searchedDomain.isEmpty { |
| @@ -132,7 +132,7 @@ struct AuditDomainTimelineView: View { | ||
| 132 | 132 | } |
| 133 | 133 | } |
| 134 | 134 | } |
| 135 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 135 | .listRowBackground(Color(.appSurface)) | |
| 136 | 136 | |
| 137 | 137 | Section("Trend") { |
| 138 | 138 | ForEach(viewModel.auditTimeline(for: domain)) { point in |
| @@ -152,10 +152,10 @@ struct AuditDomainTimelineView: View { | ||
| 152 | 152 | .padding(.vertical, 2) |
| 153 | 153 | } |
| 154 | 154 | } |
| 155 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 155 | .listRowBackground(Color(.appSurface)) | |
| 156 | 156 | } |
| 157 | 157 | .scrollContentBackground(.hidden) |
| 158 | .background(Color.black) | |
| 158 | .background(Color(.appBackground)) | |
| 159 | 159 | .navigationTitle(domain) |
| 160 | 160 | .toolbar { |
| 161 | 161 | ToolbarItem(placement: .topBarTrailing) { |
| @@ -218,7 +218,7 @@ struct AuditSessionDetailView: View { | ||
| 218 | 218 | .foregroundStyle(.secondary) |
| 219 | 219 | } |
| 220 | 220 | } |
| 221 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 221 | .listRowBackground(Color(.appSurface)) | |
| 222 | 222 | |
| 223 | 223 | Section("Audit Summary") { |
| 224 | 224 | LabeledContent("Findings", value: "\(session.findings.count)") |
| @@ -228,7 +228,7 @@ struct AuditSessionDetailView: View { | ||
| 228 | 228 | LabeledContent("Highest Severity", value: severity.title) |
| 229 | 229 | } |
| 230 | 230 | } |
| 231 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 231 | .listRowBackground(Color(.appSurface)) | |
| 232 | 232 | |
| 233 | 233 | Section("Reviewer Notes") { |
| 234 | 234 | TextEditor(text: $notesDraft) |
| @@ -240,7 +240,7 @@ struct AuditSessionDetailView: View { | ||
| 240 | 240 | viewModel.updateAuditNotes(notesDraft, sessionID: session.id) |
| 241 | 241 | } |
| 242 | 242 | } |
| 243 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 243 | .listRowBackground(Color(.appSurface)) | |
| 244 | 244 | |
| 245 | 245 | Section("Checklist") { |
| 246 | 246 | ForEach(session.checklist) { item in |
| @@ -249,7 +249,7 @@ struct AuditSessionDetailView: View { | ||
| 249 | 249 | } label: { |
| 250 | 250 | HStack(alignment: .top, spacing: 10) { |
| 251 | 251 | Image(systemName: item.isComplete ? "checkmark.circle.fill" : "circle") |
| 252 | .foregroundStyle(item.isComplete ? Color.green : .secondary) | |
| 252 | .foregroundStyle(item.isComplete ? Color(.statusPositive) : .secondary) | |
| 253 | 253 | VStack(alignment: .leading, spacing: 4) { |
| 254 | 254 | Text(item.title) |
| 255 | 255 | .font(appDensity.font(.callout)) |
| @@ -264,7 +264,7 @@ struct AuditSessionDetailView: View { | ||
| 264 | 264 | .buttonStyle(.plain) |
| 265 | 265 | } |
| 266 | 266 | } |
| 267 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 267 | .listRowBackground(Color(.appSurface)) | |
| 268 | 268 | |
| 269 | 269 | Section("Findings") { |
| 270 | 270 | if session.findings.isEmpty { |
| @@ -301,7 +301,7 @@ struct AuditSessionDetailView: View { | ||
| 301 | 301 | } |
| 302 | 302 | } |
| 303 | 303 | } |
| 304 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 304 | .listRowBackground(Color(.appSurface)) | |
| 305 | 305 | |
| 306 | 306 | Section("Evidence Snapshot") { |
| 307 | 307 | LabeledContent("Availability", value: availabilityTitle(session.evidence.report.availability)) |
| @@ -311,7 +311,7 @@ struct AuditSessionDetailView: View { | ||
| 311 | 311 | LabeledContent("Reachability", value: session.evidence.report.network.reachabilitySummary) |
| 312 | 312 | LabeledContent("Registrar", value: session.evidence.report.ownership?.registrar ?? "Unavailable") |
| 313 | 313 | } |
| 314 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 314 | .listRowBackground(Color(.appSurface)) | |
| 315 | 315 | |
| 316 | 316 | if !session.evidence.historicalContext.isEmpty { |
| 317 | 317 | Section("Historical Context") { |
| @@ -325,7 +325,7 @@ struct AuditSessionDetailView: View { | ||
| 325 | 325 | } |
| 326 | 326 | } |
| 327 | 327 | } |
| 328 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 328 | .listRowBackground(Color(.appSurface)) | |
| 329 | 329 | } |
| 330 | 330 | |
| 331 | 331 | Section("Audit Timeline") { |
| @@ -345,10 +345,10 @@ struct AuditSessionDetailView: View { | ||
| 345 | 345 | } |
| 346 | 346 | } |
| 347 | 347 | } |
| 348 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 348 | .listRowBackground(Color(.appSurface)) | |
| 349 | 349 | } |
| 350 | 350 | .scrollContentBackground(.hidden) |
| 351 | .background(Color.black) | |
| 351 | .background(Color(.appBackground)) | |
| 352 | 352 | .navigationTitle("Audit Session") |
| 353 | 353 | .toolbar { |
| 354 | 354 | ToolbarItemGroup(placement: .topBarTrailing) { |
| @@ -398,7 +398,7 @@ struct AuditSessionDetailView: View { | ||
| 398 | 398 | } |
| 399 | 399 | } else { |
| 400 | 400 | ContentUnavailableView("Audit Session Missing", systemImage: "exclamationmark.triangle") |
| 401 | .background(Color.black) | |
| 401 | .background(Color(.appBackground)) | |
| 402 | 402 | } |
| 403 | 403 | } |
| 404 | 404 | .preferredColorScheme(.dark) |
| @@ -477,7 +477,7 @@ private struct AuditFindingEditorView: View { | ||
| 477 | 477 | Text(area.title) |
| 478 | 478 | Spacer() |
| 479 | 479 | Image(systemName: selectedAreas.contains(area) ? "checkmark.circle.fill" : "circle") |
| 480 | .foregroundStyle(selectedAreas.contains(area) ? Color.green : .secondary) | |
| 480 | .foregroundStyle(selectedAreas.contains(area) ? Color(.statusPositive) : .secondary) | |
| 481 | 481 | } |
| 482 | 482 | } |
| 483 | 483 | .buttonStyle(.plain) |
| @@ -528,11 +528,11 @@ private func auditStatusBadge(_ status: AuditStatus) -> some View { | ||
| 528 | 528 | let model: AppStatusBadgeModel |
| 529 | 529 | switch status { |
| 530 | 530 | case .draft: |
| 531 | model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 531 | model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 532 | 532 | case .inReview: |
| 533 | model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) | |
| 533 | model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) | |
| 534 | 534 | case .complete: |
| 535 | model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 535 | model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 536 | 536 | } |
| 537 | 537 | return AppStatusBadgeView(model: model) |
| 538 | 538 | } |
| @@ -543,11 +543,11 @@ private func findingSeverityBadge(_ severity: AuditFindingSeverity) -> some View | ||
| 543 | 543 | case .informational: |
| 544 | 544 | color = .secondary |
| 545 | 545 | case .low: |
| 546 | color = .green | |
| 546 | color = Color(.statusPositive) | |
| 547 | 547 | case .medium: |
| 548 | color = .yellow | |
| 548 | color = Color(.statusWarning) | |
| 549 | 549 | case .high: |
| 550 | color = .red | |
| 550 | color = Color(.statusCritical) | |
| 551 | 551 | } |
| 552 | 552 | return AppStatusBadgeView( |
| 553 | 553 | model: .init( |
DomainDig/DomainDigUI.swift +21 −21
| @@ -92,48 +92,48 @@ enum AppStatusFactory { | ||
| 92 | 92 | static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel { |
| 93 | 93 | switch status { |
| 94 | 94 | case .available: |
| 95 | return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 95 | return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 96 | 96 | case .registered: |
| 97 | return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 97 | return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 98 | 98 | case .unknown, .none: |
| 99 | return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 99 | return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 100 | 100 | } |
| 101 | 101 | } |
| 102 | 102 | |
| 103 | 103 | static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel { |
| 104 | 104 | if error != nil || sslInfo == nil { |
| 105 | return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 105 | return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 106 | 106 | } |
| 107 | 107 | if let sslInfo, sslInfo.daysUntilExpiry <= 14 { |
| 108 | return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 108 | return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 109 | 109 | } |
| 110 | return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 110 | return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 111 | 111 | } |
| 112 | 112 | |
| 113 | 113 | static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel { |
| 114 | 114 | guard error == nil, let result else { |
| 115 | return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 115 | return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 116 | 116 | } |
| 117 | 117 | |
| 118 | 118 | let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count |
| 119 | 119 | switch foundCount { |
| 120 | 120 | case 3: |
| 121 | return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 121 | return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 122 | 122 | case 1, 2: |
| 123 | return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 123 | return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 124 | 124 | default: |
| 125 | return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 125 | return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 126 | 126 | } |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | 129 | static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel { |
| 130 | 130 | guard let summary else { |
| 131 | return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 131 | return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 132 | 132 | } |
| 133 | 133 | if summary.hasChanges { |
| 134 | return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) | |
| 134 | return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) | |
| 135 | 135 | } |
| 136 | return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) | |
| 136 | return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) | |
| 137 | 137 | } |
| 138 | 138 | } |
| 139 | 139 | |
| @@ -184,9 +184,9 @@ struct AppCopyButton: View { | ||
| 184 | 184 | } label: { |
| 185 | 185 | Image(systemName: didCopy ? "checkmark" : "doc.on.doc") |
| 186 | 186 | .font(appDensity.font(.caption)) |
| 187 | .foregroundStyle(didCopy ? Color.green : .secondary) | |
| 187 | .foregroundStyle(didCopy ? Color(.statusPositive) : .secondary) | |
| 188 | 188 | .frame(width: 30, height: 30) |
| 189 | .background(Color(.systemGray5).opacity(0.35)) | |
| 189 | .background(Color(.appSurfaceElevated)) | |
| 190 | 190 | .clipShape(RoundedRectangle(cornerRadius: 8)) |
| 191 | 191 | } |
| 192 | 192 | .buttonStyle(.plain) |
| @@ -264,11 +264,11 @@ struct EmptyStateCardView: View { | ||
| 264 | 264 | |
| 265 | 265 | Text(suggestion) |
| 266 | 266 | .font(appDensity.font(.caption)) |
| 267 | .foregroundStyle(.cyan) | |
| 267 | .foregroundStyle(Color(.statusInfo)) | |
| 268 | 268 | } |
| 269 | 269 | .frame(maxWidth: .infinity, alignment: .leading) |
| 270 | 270 | .padding(appDensity.metrics.cardPadding) |
| 271 | .background(showsCardBackground ? Color(.systemGray6).opacity(0.45) : Color.clear) | |
| 271 | .background(showsCardBackground ? Color(.appSurface) : Color.clear) | |
| 272 | 272 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 273 | 273 | } |
| 274 | 274 | } |
| @@ -307,7 +307,7 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View { | ||
| 307 | 307 | VStack(alignment: .leading, spacing: 3) { |
| 308 | 308 | Text(title) |
| 309 | 309 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) |
| 310 | .foregroundStyle(.white) | |
| 310 | .foregroundStyle(.primary) | |
| 311 | 311 | if let subtitle { |
| 312 | 312 | Text(subtitle) |
| 313 | 313 | .font(appDensity.font(.caption)) |
| @@ -346,7 +346,7 @@ struct TagChipRowView: View { | ||
| 346 | 346 | .font(.caption) |
| 347 | 347 | .padding(.horizontal, 10) |
| 348 | 348 | .padding(.vertical, 5) |
| 349 | .background(Color(.systemGray5).opacity(0.6), in: Capsule()) | |
| 349 | .background(Color(.appSurfaceElevated), in: Capsule()) | |
| 350 | 350 | } |
| 351 | 351 | } |
| 352 | 352 | } |
| @@ -380,8 +380,8 @@ struct TagFilterChipRowView: View { | ||
| 380 | 380 | .font(.caption) |
| 381 | 381 | .padding(.horizontal, 10) |
| 382 | 382 | .padding(.vertical, 5) |
| 383 | .background(isSelected ? Color.cyan.opacity(0.3) : Color(.systemGray5).opacity(0.6), in: Capsule()) | |
| 384 | .foregroundStyle(isSelected ? Color.cyan : Color.primary) | |
| 383 | .background(isSelected ? Color(.statusInfo).opacity(0.3) : Color(.appSurfaceElevated), in: Capsule()) | |
| 384 | .foregroundStyle(isSelected ? Color(.statusInfo) : Color.primary) | |
| 385 | 385 | } |
| 386 | 386 | .buttonStyle(.plain) |
| 387 | 387 | } |
DomainDig/HistoryView.swift +9 −9
| @@ -25,7 +25,7 @@ struct HistoryView: View { | ||
| 25 | 25 | systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90", |
| 26 | 26 | showsCardBackground: false |
| 27 | 27 | ) |
| 28 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 28 | .listRowBackground(Color(.appSurface)) | |
| 29 | 29 | } else { |
| 30 | 30 | Section("Domains") { |
| 31 | 31 | ForEach(domainSummaries, id: \.domain) { item in |
| @@ -55,21 +55,21 @@ struct HistoryView: View { | ||
| 55 | 55 | model: .init( |
| 56 | 56 | title: severity.title, |
| 57 | 57 | systemImage: "arrow.triangle.2.circlepath", |
| 58 | foregroundColor: severity == .high ? .red : .yellow, | |
| 59 | backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16) | |
| 58 | foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), | |
| 59 | backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16) | |
| 60 | 60 | ) |
| 61 | 61 | ) |
| 62 | 62 | } |
| 63 | 63 | } |
| 64 | 64 | } |
| 65 | 65 | } |
| 66 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 66 | .listRowBackground(Color(.appSurface)) | |
| 67 | 67 | } |
| 68 | 68 | } |
| 69 | 69 | } |
| 70 | 70 | } |
| 71 | 71 | .scrollContentBackground(.hidden) |
| 72 | .background(Color.black) | |
| 72 | .background(Color(.appBackground)) | |
| 73 | 73 | .navigationTitle("History") |
| 74 | 74 | .searchable(text: $viewModel.timelineDomainFilter, prompt: "Search domains") |
| 75 | 75 | .toolbar { |
| @@ -321,7 +321,7 @@ struct HistoryDetailView: View { | ||
| 321 | 321 | .padding(.horizontal) |
| 322 | 322 | .padding(.bottom, 32) |
| 323 | 323 | } |
| 324 | .background(Color.black) | |
| 324 | .background(Color(.appBackground)) | |
| 325 | 325 | .navigationTitle(entry.domain) |
| 326 | 326 | .toolbar { |
| 327 | 327 | ToolbarItemGroup(placement: .topBarTrailing) { |
| @@ -390,12 +390,12 @@ struct HistoryDetailView: View { | ||
| 390 | 390 | if let mismatchNote = viewModel.resolverMismatchNote(for: entry) { |
| 391 | 391 | Text(mismatchNote) |
| 392 | 392 | .font(appDensity.font(.caption2)) |
| 393 | .foregroundStyle(.orange) | |
| 393 | .foregroundStyle(Color(.statusWarning)) | |
| 394 | 394 | } |
| 395 | 395 | if entry.isPartialSnapshot { |
| 396 | 396 | Text("Partial snapshot: \(entry.validationIssues.joined(separator: " | "))") |
| 397 | 397 | .font(appDensity.font(.caption2)) |
| 398 | .foregroundStyle(.yellow) | |
| 398 | .foregroundStyle(Color(.statusWarning)) | |
| 399 | 399 | } |
| 400 | 400 | if let note = entry.note, !note.isEmpty { |
| 401 | 401 | Text(note) |
| @@ -406,7 +406,7 @@ struct HistoryDetailView: View { | ||
| 406 | 406 | .foregroundStyle(.secondary) |
| 407 | 407 | .padding(8) |
| 408 | 408 | .frame(maxWidth: .infinity, alignment: .leading) |
| 409 | .background(Color(.systemGray6).opacity(0.3)) | |
| 409 | .background(Color(.appSurface)) | |
| 410 | 410 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 411 | 411 | .padding(.vertical, 12) |
| 412 | 412 | } |
DomainDig/IntegrationsView.swift +3 −3
| @@ -52,7 +52,7 @@ struct IntegrationsSettingsView: View { | ||
| 52 | 52 | if !target.isEnabled { |
| 53 | 53 | Text("Disabled") |
| 54 | 54 | .font(.caption2) |
| 55 | .foregroundStyle(.orange) | |
| 55 | .foregroundStyle(Color(.statusWarning)) | |
| 56 | 56 | } |
| 57 | 57 | } |
| 58 | 58 | } |
| @@ -158,7 +158,7 @@ private struct IntegrationDetailView: View { | ||
| 158 | 158 | .foregroundStyle(.secondary) |
| 159 | 159 | |
| 160 | 160 | if let failureReason = record.failureReason { |
| 161 | let failureColor: Color = record.status == .skipped ? .secondary : .red | |
| 161 | let failureColor: Color = record.status == .skipped ? .secondary : Color(.statusCritical) | |
| 162 | 162 | Text(failureReason) |
| 163 | 163 | .font(.caption) |
| 164 | 164 | .foregroundStyle(failureColor) |
| @@ -324,7 +324,7 @@ private struct IntegrationEditorView: View { | ||
| 324 | 324 | Section { |
| 325 | 325 | Text(validationMessage) |
| 326 | 326 | .font(.caption) |
| 327 | .foregroundStyle(.red) | |
| 327 | .foregroundStyle(Color(.statusCritical)) | |
| 328 | 328 | } |
| 329 | 329 | } |
| 330 | 330 | } |
DomainDig/MonitoringView.swift +14 −14
| @@ -41,7 +41,7 @@ struct MonitoringView: View { | ||
| 41 | 41 | } |
| 42 | 42 | .padding(.vertical, 4) |
| 43 | 43 | } |
| 44 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 44 | .listRowBackground(Color(.appSurface)) | |
| 45 | 45 | |
| 46 | 46 | if viewModel.monitoringLogs.isEmpty { |
| 47 | 47 | Section { |
| @@ -53,7 +53,7 @@ struct MonitoringView: View { | ||
| 53 | 53 | showsCardBackground: false |
| 54 | 54 | ) |
| 55 | 55 | } |
| 56 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 56 | .listRowBackground(Color(.appSurface)) | |
| 57 | 57 | } else { |
| 58 | 58 | Section("Recent Runs") { |
| 59 | 59 | ForEach(viewModel.monitoringLogs) { log in |
| @@ -78,10 +78,10 @@ struct MonitoringView: View { | ||
| 78 | 78 | HStack(spacing: 8) { |
| 79 | 79 | metricBadge(title: "\(log.domainsChecked) checked") |
| 80 | 80 | if log.changesFound > 0 { |
| 81 | metricBadge(title: "\(log.changesFound) changed", tint: .orange) | |
| 81 | metricBadge(title: "\(log.changesFound) changed", tint: Color(.statusWarning)) | |
| 82 | 82 | } |
| 83 | 83 | if log.alertsTriggered > 0 { |
| 84 | metricBadge(title: "\(log.alertsTriggered) alerts", tint: .red) | |
| 84 | metricBadge(title: "\(log.alertsTriggered) alerts", tint: Color(.statusCritical)) | |
| 85 | 85 | } |
| 86 | 86 | } |
| 87 | 87 | } |
| @@ -89,11 +89,11 @@ struct MonitoringView: View { | ||
| 89 | 89 | } |
| 90 | 90 | } |
| 91 | 91 | } |
| 92 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 92 | .listRowBackground(Color(.appSurface)) | |
| 93 | 93 | } |
| 94 | 94 | } |
| 95 | 95 | .scrollContentBackground(.hidden) |
| 96 | .background(Color.black) | |
| 96 | .background(Color(.appBackground)) | |
| 97 | 97 | .navigationTitle("Monitoring") |
| 98 | 98 | .preferredColorScheme(.dark) |
| 99 | 99 | .onAppear { |
| @@ -101,7 +101,7 @@ struct MonitoringView: View { | ||
| 101 | 101 | } |
| 102 | 102 | } |
| 103 | 103 | |
| 104 | private func metricBadge(title: String, tint: Color = .cyan) -> some View { | |
| 104 | private func metricBadge(title: String, tint: Color = Color(.statusInfo)) -> some View { | |
| 105 | 105 | Text(title) |
| 106 | 106 | .font(appDensity.font(.caption2)) |
| 107 | 107 | .foregroundStyle(tint) |
| @@ -133,7 +133,7 @@ struct MonitoringLogDetailView: View { | ||
| 133 | 133 | LabeledContent("Alerts", value: "\(log.alertsTriggered)") |
| 134 | 134 | LabeledContent("Timestamp", value: log.timestamp.formatted(date: .abbreviated, time: .shortened)) |
| 135 | 135 | } |
| 136 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 136 | .listRowBackground(Color(.appSurface)) | |
| 137 | 137 | |
| 138 | 138 | Section("Domains") { |
| 139 | 139 | ForEach(log.checkedDomains) { result in |
| @@ -167,7 +167,7 @@ struct MonitoringLogDetailView: View { | ||
| 167 | 167 | if let errorMessage = result.errorMessage { |
| 168 | 168 | Text(errorMessage) |
| 169 | 169 | .font(appDensity.font(.caption2)) |
| 170 | .foregroundStyle(.yellow) | |
| 170 | .foregroundStyle(Color(.statusWarning)) | |
| 171 | 171 | } |
| 172 | 172 | |
| 173 | 173 | if let historyEntryID = result.historyEntryID, |
| @@ -181,7 +181,7 @@ struct MonitoringLogDetailView: View { | ||
| 181 | 181 | .padding(.vertical, 4) |
| 182 | 182 | } |
| 183 | 183 | } |
| 184 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 184 | .listRowBackground(Color(.appSurface)) | |
| 185 | 185 | |
| 186 | 186 | if !log.errors.isEmpty { |
| 187 | 187 | Section("Errors") { |
| @@ -191,11 +191,11 @@ struct MonitoringLogDetailView: View { | ||
| 191 | 191 | .foregroundStyle(.secondary) |
| 192 | 192 | } |
| 193 | 193 | } |
| 194 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 194 | .listRowBackground(Color(.appSurface)) | |
| 195 | 195 | } |
| 196 | 196 | } |
| 197 | 197 | .scrollContentBackground(.hidden) |
| 198 | .background(Color.black) | |
| 198 | .background(Color(.appBackground)) | |
| 199 | 199 | .navigationTitle("Run Details") |
| 200 | 200 | .preferredColorScheme(.dark) |
| 201 | 201 | } |
| @@ -205,9 +205,9 @@ struct MonitoringLogDetailView: View { | ||
| 205 | 205 | case .info: |
| 206 | 206 | return .secondary |
| 207 | 207 | case .warning: |
| 208 | return .yellow | |
| 208 | return Color(.statusWarning) | |
| 209 | 209 | case .critical: |
| 210 | return .red | |
| 210 | return Color(.statusCritical) | |
| 211 | 211 | } |
| 212 | 212 | } |
| 213 | 213 | } |
DomainDig/PaywallView.swift +1 −1
| @@ -75,7 +75,7 @@ struct PaywallView: View { | ||
| 75 | 75 | if let errorMessage = purchaseService.errorMessage { |
| 76 | 76 | Text(errorMessage) |
| 77 | 77 | .font(appDensity.font(.caption, design: .default)) |
| 78 | .foregroundStyle(.red) | |
| 78 | .foregroundStyle(Color(.statusCritical)) | |
| 79 | 79 | } |
| 80 | 80 | } |
| 81 | 81 | |
DomainDig/SavedDomainsView.swift +3 −3
| @@ -10,7 +10,7 @@ struct SavedDomainsView: View { | ||
| 10 | 10 | Text("No saved domains") |
| 11 | 11 | .font(.system(.callout, design: .monospaced)) |
| 12 | 12 | .foregroundStyle(.secondary) |
| 13 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 13 | .listRowBackground(Color(.appSurface)) | |
| 14 | 14 | } else { |
| 15 | 15 | ForEach(viewModel.savedDomains, id: \.self) { domain in |
| 16 | 16 | Button { |
| @@ -22,7 +22,7 @@ struct SavedDomainsView: View { | ||
| 22 | 22 | .font(.system(.callout, design: .monospaced)) |
| 23 | 23 | .foregroundStyle(.primary) |
| 24 | 24 | } |
| 25 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 25 | .listRowBackground(Color(.appSurface)) | |
| 26 | 26 | } |
| 27 | 27 | .onDelete { offsets in |
| 28 | 28 | viewModel.removeSavedDomains(at: offsets) |
| @@ -30,7 +30,7 @@ struct SavedDomainsView: View { | ||
| 30 | 30 | } |
| 31 | 31 | } |
| 32 | 32 | .scrollContentBackground(.hidden) |
| 33 | .background(Color.black) | |
| 33 | .background(Color(.appBackground)) | |
| 34 | 34 | .navigationTitle("Saved Domains") |
| 35 | 35 | .toolbar { |
| 36 | 36 | if !viewModel.savedDomains.isEmpty { |
DomainDig/ScheduledReportsView.swift +4 −4
| @@ -67,7 +67,7 @@ struct ScheduledReportsView: View { | ||
| 67 | 67 | } |
| 68 | 68 | .disabled(isGenerating || !FeatureAccessService.hasAccess(to: .automatedMonitoring)) |
| 69 | 69 | } |
| 70 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 70 | .listRowBackground(Color(.appSurface)) | |
| 71 | 71 | |
| 72 | 72 | if logs.isEmpty { |
| 73 | 73 | Section { |
| @@ -79,7 +79,7 @@ struct ScheduledReportsView: View { | ||
| 79 | 79 | showsCardBackground: false |
| 80 | 80 | ) |
| 81 | 81 | } |
| 82 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 82 | .listRowBackground(Color(.appSurface)) | |
| 83 | 83 | } else { |
| 84 | 84 | Section("Recent Reports") { |
| 85 | 85 | ForEach(logs) { log in |
| @@ -96,11 +96,11 @@ struct ScheduledReportsView: View { | ||
| 96 | 96 | } |
| 97 | 97 | } |
| 98 | 98 | } |
| 99 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 99 | .listRowBackground(Color(.appSurface)) | |
| 100 | 100 | } |
| 101 | 101 | } |
| 102 | 102 | .scrollContentBackground(.hidden) |
| 103 | .background(Color.black) | |
| 103 | .background(Color(.appBackground)) | |
| 104 | 104 | .navigationTitle("Scheduled Reports") |
| 105 | 105 | } |
| 106 | 106 | |
DomainDig/TimelineView.swift +5 −5
| @@ -45,12 +45,12 @@ struct TimelineView: View { | ||
| 45 | 45 | } |
| 46 | 46 | } |
| 47 | 47 | } |
| 48 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 48 | .listRowBackground(Color(.appSurface)) | |
| 49 | 49 | } |
| 50 | 50 | } |
| 51 | 51 | } |
| 52 | 52 | .scrollContentBackground(.hidden) |
| 53 | .background(Color.black) | |
| 53 | .background(Color(.appBackground)) | |
| 54 | 54 | .navigationTitle(domain) |
| 55 | 55 | .toolbar { |
| 56 | 56 | ToolbarItemGroup(placement: .topBarTrailing) { |
| @@ -116,8 +116,8 @@ private struct TimelineRow: View { | ||
| 116 | 116 | model: .init( |
| 117 | 117 | title: severity.title, |
| 118 | 118 | systemImage: "arrow.triangle.2.circlepath", |
| 119 | foregroundColor: severity == .high ? .red : .yellow, | |
| 120 | backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16) | |
| 119 | foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), | |
| 120 | backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16) | |
| 121 | 121 | ) |
| 122 | 122 | ) |
| 123 | 123 | } |
| @@ -203,7 +203,7 @@ struct TimelineDiffView: View { | ||
| 203 | 203 | } |
| 204 | 204 | .padding() |
| 205 | 205 | } |
| 206 | .background(Color.black) | |
| 206 | .background(Color(.appBackground)) | |
| 207 | 207 | .navigationTitle("Compare Snapshots") |
| 208 | 208 | .navigationBarTitleDisplayMode(.inline) |
| 209 | 209 | .onAppear { |
DomainDig/WatchlistView.swift +23 −23
| @@ -38,7 +38,7 @@ struct WatchlistView: View { | ||
| 38 | 38 | Section("Refresh Progress") { |
| 39 | 39 | VStack(alignment: .leading, spacing: 8) { |
| 40 | 40 | ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) |
| 41 | .tint(.cyan) | |
| 41 | .tint(Color(.statusInfo)) | |
| 42 | 42 | HStack { |
| 43 | 43 | Text(viewModel.batchProgressLabel) |
| 44 | 44 | .font(appDensity.font(.caption)) |
| @@ -59,7 +59,7 @@ struct WatchlistView: View { | ||
| 59 | 59 | } |
| 60 | 60 | .padding(.vertical, 4) |
| 61 | 61 | } |
| 62 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 62 | .listRowBackground(Color(.appSurface)) | |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | 65 | if viewModel.filteredTrackedDomains.isEmpty { |
| @@ -72,7 +72,7 @@ struct WatchlistView: View { | ||
| 72 | 72 | showsCardBackground: false |
| 73 | 73 | ) |
| 74 | 74 | } |
| 75 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 75 | .listRowBackground(Color(.appSurface)) | |
| 76 | 76 | } else { |
| 77 | 77 | if let limitMessage = FeatureAccessService.trackedDomainLimitMessage(currentCount: viewModel.trackedDomains.count) { |
| 78 | 78 | Section { |
| @@ -80,7 +80,7 @@ struct WatchlistView: View { | ||
| 80 | 80 | .font(appDensity.font(.caption)) |
| 81 | 81 | .foregroundStyle(.secondary) |
| 82 | 82 | } |
| 83 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 83 | .listRowBackground(Color(.appSurface)) | |
| 84 | 84 | } |
| 85 | 85 | |
| 86 | 86 | if !pinnedDomains.isEmpty { |
| @@ -94,7 +94,7 @@ struct WatchlistView: View { | ||
| 94 | 94 | } |
| 95 | 95 | .animation(.easeInOut(duration: 0.2), value: viewModel.filteredTrackedDomains.map(\.id)) |
| 96 | 96 | .scrollContentBackground(.hidden) |
| 97 | .background(Color.black) | |
| 97 | .background(Color(.appBackground)) | |
| 98 | 98 | .navigationTitle("Watchlist") |
| 99 | 99 | .searchable(text: $viewModel.watchlistSearchText, prompt: "Search tracked domains") |
| 100 | 100 | .toolbar { |
| @@ -215,7 +215,7 @@ struct WatchlistView: View { | ||
| 215 | 215 | Section { |
| 216 | 216 | Text(addDomainError) |
| 217 | 217 | .font(appDensity.font(.caption)) |
| 218 | .foregroundStyle(.red) | |
| 218 | .foregroundStyle(Color(.statusCritical)) | |
| 219 | 219 | } |
| 220 | 220 | } |
| 221 | 221 | } |
| @@ -317,14 +317,14 @@ struct WatchlistView: View { | ||
| 317 | 317 | } label: { |
| 318 | 318 | Label("Refresh", systemImage: "arrow.clockwise") |
| 319 | 319 | } |
| 320 | .tint(.cyan) | |
| 320 | .tint(Color(.statusInfo)) | |
| 321 | 321 | |
| 322 | 322 | Button { |
| 323 | 323 | viewModel.togglePinned(for: trackedDomain) |
| 324 | 324 | } label: { |
| 325 | 325 | Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin") |
| 326 | 326 | } |
| 327 | .tint(.yellow) | |
| 327 | .tint(Color(.statusWarning)) | |
| 328 | 328 | } |
| 329 | 329 | .swipeActions(edge: .trailing, allowsFullSwipe: false) { |
| 330 | 330 | Button { |
| @@ -333,7 +333,7 @@ struct WatchlistView: View { | ||
| 333 | 333 | } label: { |
| 334 | 334 | Label("Refresh", systemImage: "arrow.clockwise") |
| 335 | 335 | } |
| 336 | .tint(.cyan) | |
| 336 | .tint(Color(.statusInfo)) | |
| 337 | 337 | |
| 338 | 338 | if viewModel.canDelete(trackedDomain) { |
| 339 | 339 | Button(role: .destructive) { |
| @@ -380,7 +380,7 @@ struct WatchlistView: View { | ||
| 380 | 380 | } |
| 381 | 381 | } |
| 382 | 382 | } |
| 383 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 383 | .listRowBackground(Color(.appSurface)) | |
| 384 | 384 | } |
| 385 | 385 | |
| 386 | 386 | private var batchSummaryBinding: Binding<BatchSweepSummary?> { |
| @@ -437,7 +437,7 @@ struct WatchlistRowView: View { | ||
| 437 | 437 | if trackedDomain.isPinned { |
| 438 | 438 | Image(systemName: "pin.fill") |
| 439 | 439 | .font(.caption2) |
| 440 | .foregroundStyle(.yellow) | |
| 440 | .foregroundStyle(Color(.statusWarning)) | |
| 441 | 441 | } |
| 442 | 442 | Text(trackedDomain.domain) |
| 443 | 443 | .font(appDensity.font(.callout)) |
| @@ -502,7 +502,7 @@ struct WatchlistRowView: View { | ||
| 502 | 502 | @ViewBuilder |
| 503 | 503 | private var statusBadge: some View { |
| 504 | 504 | if isRefreshing { |
| 505 | AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.6))) | |
| 505 | AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))) | |
| 506 | 506 | } else { |
| 507 | 507 | AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability)) |
| 508 | 508 | } |
| @@ -516,8 +516,8 @@ struct WatchlistRowView: View { | ||
| 516 | 516 | model: .init( |
| 517 | 517 | title: "Shared", |
| 518 | 518 | systemImage: "person.2.fill", |
| 519 | foregroundColor: .cyan, | |
| 520 | backgroundColor: .cyan.opacity(0.16) | |
| 519 | foregroundColor: Color(.statusInfo), | |
| 520 | backgroundColor: Color(.statusInfo).opacity(0.16) | |
| 521 | 521 | ) |
| 522 | 522 | ) |
| 523 | 523 | } |
| @@ -534,11 +534,11 @@ struct WatchlistRowView: View { | ||
| 534 | 534 | let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon" |
| 535 | 535 | switch trackedDomain.certificateWarningLevel { |
| 536 | 536 | case .critical: |
| 537 | return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) | |
| 537 | return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | |
| 538 | 538 | case .warning: |
| 539 | return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) | |
| 539 | return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | |
| 540 | 540 | case .none: |
| 541 | return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) | |
| 541 | return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) | |
| 542 | 542 | } |
| 543 | 543 | } |
| 544 | 544 | } |
| @@ -577,7 +577,7 @@ struct TrackedDomainDetailView: View { | ||
| 577 | 577 | isRefreshing: viewModel.refreshingTrackedDomainID == liveTrackedDomain.id |
| 578 | 578 | ) |
| 579 | 579 | } |
| 580 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 580 | .listRowBackground(Color(.appSurface)) | |
| 581 | 581 | |
| 582 | 582 | Section { |
| 583 | 583 | Button { |
| @@ -652,13 +652,13 @@ struct TrackedDomainDetailView: View { | ||
| 652 | 652 | Label(liveTrackedDomain.collaboration?.isShared == true ? "Manage Share" : "Share Domain", systemImage: "person.2") |
| 653 | 653 | } |
| 654 | 654 | } |
| 655 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 655 | .listRowBackground(Color(.appSurface)) | |
| 656 | 656 | |
| 657 | 657 | if !liveTrackedDomain.tags.isEmpty { |
| 658 | 658 | Section("Tags") { |
| 659 | 659 | TagChipRowView(tags: liveTrackedDomain.tags) |
| 660 | 660 | } |
| 661 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 661 | .listRowBackground(Color(.appSurface)) | |
| 662 | 662 | } |
| 663 | 663 | |
| 664 | 664 | Section("Monitoring Status") { |
| @@ -672,7 +672,7 @@ struct TrackedDomainDetailView: View { | ||
| 672 | 672 | LabeledContent("Queued Alerts", value: "\(liveTrackedDomain.pendingMonitoringAlerts.count)") |
| 673 | 673 | } |
| 674 | 674 | } |
| 675 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 675 | .listRowBackground(Color(.appSurface)) | |
| 676 | 676 | |
| 677 | 677 | if let summary = viewModel.latestChangeSummary(for: liveTrackedDomain) { |
| 678 | 678 | Section("Latest Change Summary") { |
| @@ -718,10 +718,10 @@ struct TrackedDomainDetailView: View { | ||
| 718 | 718 | } |
| 719 | 719 | } |
| 720 | 720 | } |
| 721 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 721 | .listRowBackground(Color(.appSurface)) | |
| 722 | 722 | } |
| 723 | 723 | .scrollContentBackground(.hidden) |
| 724 | .background(Color.black) | |
| 724 | .background(Color(.appBackground)) | |
| 725 | 725 | .navigationTitle(liveTrackedDomain.domain) |
| 726 | 726 | .preferredColorScheme(.dark) |
| 727 | 727 | .onChange(of: viewModel.rerunNavigationToken) { _, _ in |
DomainDig/WorkflowsView.swift +9 −9
| @@ -24,13 +24,13 @@ struct WorkflowsView: View { | ||
| 24 | 24 | .font(appDensity.font(.caption)) |
| 25 | 25 | .foregroundStyle(.secondary) |
| 26 | 26 | } |
| 27 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 27 | .listRowBackground(Color(.appSurface)) | |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | workflowContent |
| 31 | 31 | } |
| 32 | 32 | .scrollContentBackground(.hidden) |
| 33 | .background(Color.black) | |
| 33 | .background(Color(.appBackground)) | |
| 34 | 34 | .refreshable { |
| 35 | 35 | await viewModel.refreshWorkflowList() |
| 36 | 36 | } |
| @@ -73,7 +73,7 @@ struct WorkflowsView: View { | ||
| 73 | 73 | value: Double(viewModel.batchCompletedCount), |
| 74 | 74 | total: Double(max(viewModel.batchTotalCount, 1)) |
| 75 | 75 | ) |
| 76 | .tint(.cyan) | |
| 76 | .tint(Color(.statusInfo)) | |
| 77 | 77 | |
| 78 | 78 | HStack { |
| 79 | 79 | Text(viewModel.batchProgressLabel) |
| @@ -91,7 +91,7 @@ struct WorkflowsView: View { | ||
| 91 | 91 | } |
| 92 | 92 | .padding(.vertical, 4) |
| 93 | 93 | } |
| 94 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 94 | .listRowBackground(Color(.appSurface)) | |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | 97 | if viewModel.workflows.isEmpty { |
| @@ -104,7 +104,7 @@ struct WorkflowsView: View { | ||
| 104 | 104 | showsCardBackground: false |
| 105 | 105 | ) |
| 106 | 106 | } |
| 107 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 107 | .listRowBackground(Color(.appSurface)) | |
| 108 | 108 | } else { |
| 109 | 109 | ForEach(viewModel.workflows) { workflow in |
| 110 | 110 | NavigationLink { |
| @@ -112,7 +112,7 @@ struct WorkflowsView: View { | ||
| 112 | 112 | } label: { |
| 113 | 113 | WorkflowRowView(workflow: workflow) |
| 114 | 114 | } |
| 115 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | |
| 115 | .listRowBackground(Color(.appSurface)) | |
| 116 | 116 | } |
| 117 | 117 | .onDelete { offsets in |
| 118 | 118 | let workflows = offsets.map { viewModel.workflows[$0] } |
| @@ -133,7 +133,7 @@ private struct WorkflowRowView: View { | ||
| 133 | 133 | if workflow.collaboration?.isShared == true { |
| 134 | 134 | Image(systemName: "person.2.fill") |
| 135 | 135 | .font(.caption2) |
| 136 | .foregroundStyle(.cyan) | |
| 136 | .foregroundStyle(Color(.statusInfo)) | |
| 137 | 137 | } |
| 138 | 138 | Text(workflow.name) |
| 139 | 139 | .font(appDensity.font(.callout, design: .default, weight: .semibold)) |
| @@ -335,7 +335,7 @@ struct WorkflowDetailView: View { | ||
| 335 | 335 | } |
| 336 | 336 | } |
| 337 | 337 | .scrollContentBackground(.hidden) |
| 338 | .background(Color.black) | |
| 338 | .background(Color(.appBackground)) | |
| 339 | 339 | .navigationTitle(workflow.name) |
| 340 | 340 | .toolbar { |
| 341 | 341 | if !workflow.domains.isEmpty { |
| @@ -640,7 +640,7 @@ struct WorkflowBulkAddSheet: View { | ||
| 640 | 640 | .foregroundStyle(.primary) |
| 641 | 641 | Spacer() |
| 642 | 642 | Image(systemName: selectedDomains.contains(domain) ? "checkmark.circle.fill" : "circle") |
| 643 | .foregroundStyle(selectedDomains.contains(domain) ? .cyan : .secondary) | |
| 643 | .foregroundStyle(selectedDomains.contains(domain) ? Color(.statusInfo) : .secondary) | |
| 644 | 644 | } |
| 645 | 645 | } |
| 646 | 646 | .buttonStyle(.plain) |
DomainDigUITests/AccessibilityAuditHarness.swift +7 −1
| @@ -87,7 +87,13 @@ enum AccessibilityAuditHarness { | ||
| 87 | 87 | try app.performAccessibilityAudit { issue in |
| 88 | 88 | let isEnforced = !enforcedAuditTypes.intersection(issue.auditType).isEmpty |
| 89 | 89 | let marker = isEnforced ? "FAIL" : "report" |
| 90 | findings.append("[\(marker)][\(name(for: issue.auditType))] \(issue.compactDescription)") | |
| 90 | // Include the element so the burndown says *what* to fix, not | |
| 91 | // just that something is wrong. | |
| 92 | let element = issue.element.map { el -> String in | |
| 93 | let label = el.label.isEmpty ? el.identifier : el.label | |
| 94 | return label.isEmpty ? "\(el.elementType)" : "\"\(label)\"" | |
| 95 | } ?? "unknown element" | |
| 96 | findings.append("[\(marker)][\(name(for: issue.auditType))] \(issue.compactDescription) — \(element)") | |
| 91 | 97 | // true suppresses the finding, false reports it as a test failure. |
| 92 | 98 | return !isEnforced |
| 93 | 99 | } |
DomainDigUITests/AccessibilityAuditTests.swift +14 −1
| @@ -17,7 +17,20 @@ final class AccessibilityAuditTests: XCTestCase { | ||
| 17 | 17 | // MARK: Per-screen audits |
| 18 | 18 | |
| 19 | 19 | func testInspectScreen() throws { |
| 20 | try auditRootTab("Inspect") | |
| 20 | let app = AccessibilityAuditHarness.launch() | |
| 21 | app.selectRootTab("Inspect") | |
| 22 | ||
| 23 | // Type a domain so the Run button is enabled. A disabled control has no | |
| 24 | // contrast requirement under WCAG 1.4.3, but the audit still flags it, | |
| 25 | // so auditing the empty state would report a false positive forever. | |
| 26 | let field = app.textFields.firstMatch | |
| 27 | if field.waitForExistence(timeout: 5) { | |
| 28 | field.tap() | |
| 29 | field.typeText("example.com") | |
| 30 | } | |
| 31 | ||
| 32 | let audited = try AccessibilityAuditHarness.audit(app, screen: "inspect", test: self) | |
| 33 | try XCTSkipUnless(audited, "Audit did not complete in time for Inspect") | |
| 21 | 34 | } |
| 22 | 35 | |
| 23 | 36 | func testDashboardScreen() throws { |
DomainDigWidget/DomainDigPortfolioWidget.swift +10 −10
| @@ -91,9 +91,9 @@ struct DomainDigWidgetView: View { | ||
| 91 | 91 | Spacer(minLength: 0) |
| 92 | 92 | |
| 93 | 93 | HStack(spacing: 10) { |
| 94 | countPill(data.healthyCount, .green) | |
| 95 | countPill(data.warningCount, .orange) | |
| 96 | countPill(data.criticalCount, .red) | |
| 94 | countPill(data.healthyCount, Color(.statusPositive)) | |
| 95 | countPill(data.warningCount, Color(.statusWarning)) | |
| 96 | countPill(data.criticalCount, Color(.statusCritical)) | |
| 97 | 97 | } |
| 98 | 98 | } |
| 99 | 99 | } |
| @@ -121,10 +121,10 @@ struct DomainDigWidgetView: View { | ||
| 121 | 121 | } |
| 122 | 122 | |
| 123 | 123 | HStack(spacing: 12) { |
| 124 | summaryStat(data.healthyCount, "Healthy", .green) | |
| 125 | summaryStat(data.warningCount, "Warning", .orange) | |
| 126 | summaryStat(data.criticalCount, "Critical", .red) | |
| 127 | summaryStat(data.expiringSoonCount, "Expiring", .yellow) | |
| 124 | summaryStat(data.healthyCount, "Healthy", Color(.statusPositive)) | |
| 125 | summaryStat(data.warningCount, "Warning", Color(.statusWarning)) | |
| 126 | summaryStat(data.criticalCount, "Critical", Color(.statusCritical)) | |
| 127 | summaryStat(data.expiringSoonCount, "Expiring", Color(.statusWarning)) | |
| 128 | 128 | } |
| 129 | 129 | |
| 130 | 130 | Divider() |
| @@ -180,9 +180,9 @@ struct DomainDigWidgetView: View { | ||
| 180 | 180 | |
| 181 | 181 | private func color(for status: DomainDigWidgetStatus) -> Color { |
| 182 | 182 | switch status { |
| 183 | case .healthy: return .green | |
| 184 | case .warning: return .orange | |
| 185 | case .critical: return .red | |
| 183 | case .healthy: return Color(.statusPositive) | |
| 184 | case .warning: return Color(.statusWarning) | |
| 185 | case .critical: return Color(.statusCritical) | |
| 186 | 186 | } |
| 187 | 187 | } |
| 188 | 188 | } |
DomainDigWidget/SweepLiveActivity.swift +6 −6
| @@ -24,7 +24,7 @@ struct SweepLiveActivity: Widget { | ||
| 24 | 24 | DynamicIslandExpandedRegion(.bottom) { |
| 25 | 25 | VStack(alignment: .leading, spacing: 4) { |
| 26 | 26 | ProgressView(value: context.state.fractionComplete) |
| 27 | .tint(.cyan) | |
| 27 | .tint(Color(.statusInfo)) | |
| 28 | 28 | if let current = context.state.currentDomain { |
| 29 | 29 | Text(current) |
| 30 | 30 | .font(.caption2) |
| @@ -35,7 +35,7 @@ struct SweepLiveActivity: Widget { | ||
| 35 | 35 | } |
| 36 | 36 | } compactLeading: { |
| 37 | 37 | Image(systemName: "arrow.trianglehead.2.clockwise") |
| 38 | .foregroundStyle(.cyan) | |
| 38 | .foregroundStyle(Color(.statusInfo)) | |
| 39 | 39 | } compactTrailing: { |
| 40 | 40 | Text("\(context.state.completed)/\(context.state.total)") |
| 41 | 41 | .font(.caption2) |
| @@ -43,7 +43,7 @@ struct SweepLiveActivity: Widget { | ||
| 43 | 43 | } minimal: { |
| 44 | 44 | ProgressView(value: context.state.fractionComplete) |
| 45 | 45 | .progressViewStyle(.circular) |
| 46 | .tint(.cyan) | |
| 46 | .tint(Color(.statusInfo)) | |
| 47 | 47 | } |
| 48 | 48 | } |
| 49 | 49 | } |
| @@ -66,7 +66,7 @@ private struct SweepActivityBannerView: View { | ||
| 66 | 66 | } |
| 67 | 67 | |
| 68 | 68 | ProgressView(value: context.state.fractionComplete) |
| 69 | .tint(.cyan) | |
| 69 | .tint(Color(.statusInfo)) | |
| 70 | 70 | |
| 71 | 71 | HStack { |
| 72 | 72 | if let current = context.state.currentDomain { |
| @@ -83,12 +83,12 @@ private struct SweepActivityBannerView: View { | ||
| 83 | 83 | if context.state.changed > 0 { |
| 84 | 84 | Text("\(context.state.changed) changed") |
| 85 | 85 | .font(.caption2) |
| 86 | .foregroundStyle(.orange) | |
| 86 | .foregroundStyle(Color(.statusWarning)) | |
| 87 | 87 | } |
| 88 | 88 | if context.state.warnings > 0 { |
| 89 | 89 | Text("\(context.state.warnings) warnings") |
| 90 | 90 | .font(.caption2) |
| 91 | .foregroundStyle(.red) | |
| 91 | .foregroundStyle(Color(.statusCritical)) | |
| 92 | 92 | } |
| 93 | 93 | } |
| 94 | 94 | } |
Shared/Colors.xcassets/AccentFill.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0x00", | |
| 9 | "green": "0x00", | |
| 10 | "blue": "0xFF", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x00", | |
| 27 | "green": "0x40", | |
| 28 | "blue": "0xDD", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0x00", | |
| 45 | "green": "0x00", | |
| 46 | "blue": "0xCC", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0x00", | |
| 67 | "green": "0x33", | |
| 68 | "blue": "0xCC", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/AppBackground.colorset/Contents.json added +38
| @@ -0,0 +1,38 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0xFF", | |
| 9 | "green": "0xFF", | |
| 10 | "blue": "0xFF", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x00", | |
| 27 | "green": "0x00", | |
| 28 | "blue": "0x00", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | } | |
| 33 | ], | |
| 34 | "info": { | |
| 35 | "author": "xcode", | |
| 36 | "version": 1 | |
| 37 | } | |
| 38 | } | |
Shared/Colors.xcassets/AppOnAccent.colorset/Contents.json added +38
| @@ -0,0 +1,38 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0xFF", | |
| 9 | "green": "0xFF", | |
| 10 | "blue": "0xFF", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x00", | |
| 27 | "green": "0x00", | |
| 28 | "blue": "0x00", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | } | |
| 33 | ], | |
| 34 | "info": { | |
| 35 | "author": "xcode", | |
| 36 | "version": 1 | |
| 37 | } | |
| 38 | } | |
Shared/Colors.xcassets/AppSeparator.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0xC6", | |
| 9 | "green": "0xC6", | |
| 10 | "blue": "0xC8", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x38", | |
| 27 | "green": "0x38", | |
| 28 | "blue": "0x3A", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0xA0", | |
| 45 | "green": "0xA0", | |
| 46 | "blue": "0xA3", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0x54", | |
| 67 | "green": "0x54", | |
| 68 | "blue": "0x56", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/AppSurface.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0xF2", | |
| 9 | "green": "0xF2", | |
| 10 | "blue": "0xF7", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x0E", | |
| 27 | "green": "0x0E", | |
| 28 | "blue": "0x0F", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0xEB", | |
| 45 | "green": "0xEB", | |
| 46 | "blue": "0xF0", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0x16", | |
| 67 | "green": "0x16", | |
| 68 | "blue": "0x18", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/AppSurfaceElevated.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0xE5", | |
| 9 | "green": "0xE5", | |
| 10 | "blue": "0xEA", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x1A", | |
| 27 | "green": "0x1A", | |
| 28 | "blue": "0x1C", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0xDC", | |
| 45 | "green": "0xDC", | |
| 46 | "blue": "0xE1", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0x23", | |
| 67 | "green": "0x23", | |
| 68 | "blue": "0x26", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/Contents.json added +6
| @@ -0,0 +1,6 @@ | ||
| 1 | { | |
| 2 | "info" : { | |
| 3 | "author" : "xcode", | |
| 4 | "version" : 1 | |
| 5 | } | |
| 6 | } | |
Shared/Colors.xcassets/StatusCritical.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0xB3", | |
| 9 | "green": "0x26", | |
| 10 | "blue": "0x1E", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0xFF", | |
| 27 | "green": "0x69", | |
| 28 | "blue": "0x61", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0x8C", | |
| 45 | "green": "0x1D", | |
| 46 | "blue": "0x16", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0xFF", | |
| 67 | "green": "0x9A", | |
| 68 | "blue": "0x94", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/StatusInfo.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0x00", | |
| 9 | "green": "0x00", | |
| 10 | "blue": "0xFF", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x4D", | |
| 27 | "green": "0xA3", | |
| 28 | "blue": "0xFF", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0x00", | |
| 45 | "green": "0x00", | |
| 46 | "blue": "0xCC", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0x7F", | |
| 67 | "green": "0xBF", | |
| 68 | "blue": "0xFF", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/StatusNeutral.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0x5A", | |
| 9 | "green": "0x5A", | |
| 10 | "blue": "0x5F", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0xA1", | |
| 27 | "green": "0xA1", | |
| 28 | "blue": "0xA6", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0x3A", | |
| 45 | "green": "0x3A", | |
| 46 | "blue": "0x3C", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0xC7", | |
| 67 | "green": "0xC7", | |
| 68 | "blue": "0xCC", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/StatusPositive.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0x14", | |
| 9 | "green": "0x6C", | |
| 10 | "blue": "0x2E", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0x30", | |
| 27 | "green": "0xD1", | |
| 28 | "blue": "0x58", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0x0B", | |
| 45 | "green": "0x50", | |
| 46 | "blue": "0x23", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0x5B", | |
| 67 | "green": "0xE5", | |
| 68 | "blue": "0x84", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |
Shared/Colors.xcassets/StatusWarning.colorset/Contents.json added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | { | |
| 2 | "colors": [ | |
| 3 | { | |
| 4 | "idiom": "universal", | |
| 5 | "color": { | |
| 6 | "color-space": "srgb", | |
| 7 | "components": { | |
| 8 | "red": "0x7A", | |
| 9 | "green": "0x56", | |
| 10 | "blue": "0x00", | |
| 11 | "alpha": "1.000" | |
| 12 | } | |
| 13 | } | |
| 14 | }, | |
| 15 | { | |
| 16 | "idiom": "universal", | |
| 17 | "appearances": [ | |
| 18 | { | |
| 19 | "appearance": "luminosity", | |
| 20 | "value": "dark" | |
| 21 | } | |
| 22 | ], | |
| 23 | "color": { | |
| 24 | "color-space": "srgb", | |
| 25 | "components": { | |
| 26 | "red": "0xFF", | |
| 27 | "green": "0xD6", | |
| 28 | "blue": "0x0A", | |
| 29 | "alpha": "1.000" | |
| 30 | } | |
| 31 | } | |
| 32 | }, | |
| 33 | { | |
| 34 | "idiom": "universal", | |
| 35 | "appearances": [ | |
| 36 | { | |
| 37 | "appearance": "contrast", | |
| 38 | "value": "high" | |
| 39 | } | |
| 40 | ], | |
| 41 | "color": { | |
| 42 | "color-space": "srgb", | |
| 43 | "components": { | |
| 44 | "red": "0x5C", | |
| 45 | "green": "0x41", | |
| 46 | "blue": "0x00", | |
| 47 | "alpha": "1.000" | |
| 48 | } | |
| 49 | } | |
| 50 | }, | |
| 51 | { | |
| 52 | "idiom": "universal", | |
| 53 | "appearances": [ | |
| 54 | { | |
| 55 | "appearance": "luminosity", | |
| 56 | "value": "dark" | |
| 57 | }, | |
| 58 | { | |
| 59 | "appearance": "contrast", | |
| 60 | "value": "high" | |
| 61 | } | |
| 62 | ], | |
| 63 | "color": { | |
| 64 | "color-space": "srgb", | |
| 65 | "components": { | |
| 66 | "red": "0xFF", | |
| 67 | "green": "0xE5", | |
| 68 | "blue": "0x66", | |
| 69 | "alpha": "1.000" | |
| 70 | } | |
| 71 | } | |
| 72 | } | |
| 73 | ], | |
| 74 | "info": { | |
| 75 | "author": "xcode", | |
| 76 | "version": 1 | |
| 77 | } | |
| 78 | } | |