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 | support — the same ground the accessibility pass tracked in | 6 | support — the same ground the accessibility pass tracked in |
| 7 | [issue #21](https://github.com/zerolabsco/domain-dig/issues/21) covers. | 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 | ## Findings are reported, not enforced | 61 | ## Findings are reported, not enforced |
| 10 | 62 | ||
| 11 | The audit surfaces violations that exist today, so failing on all of them would | 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 | ## Notes | 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 | - Audits retry up to three times. Slower machines can miss the audit's internal | 146 | - Audits retry up to three times. Slower machines can miss the audit's internal |
| 89 | deadline (`Audit failed to complete in time`, code `-56`), which is a tooling | 147 | deadline (`Audit failed to complete in time`, code `-56`), which is a tooling |
| 90 | timeout, not an app defect. A screen that still cannot be audited is reported | 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" : { | 74 | "info": { |
| 8 | "author" : "xcode", | 75 | "author": "xcode", |
| 9 | "version" : 1 | 76 | "version": 1 |
| 10 | } | 77 | } |
| 11 | } | 78 | } |
DomainDig/BatchResultsView.swift +10 −10
| @@ -13,7 +13,7 @@ struct BatchResultsView: View { | |||
| 13 | if viewModel.batchLookupRunning { | 13 | if viewModel.batchLookupRunning { |
| 14 | VStack(alignment: .trailing, spacing: 4) { | 14 | VStack(alignment: .trailing, spacing: 4) { |
| 15 | ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) | 15 | ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) |
| 16 | .tint(.cyan) | 16 | .tint(Color(.statusInfo)) |
| 17 | .frame(width: 120) | 17 | .frame(width: 120) |
| 18 | Text(viewModel.batchProgressLabel) | 18 | Text(viewModel.batchProgressLabel) |
| 19 | .font(appDensity.font(.caption2)) | 19 | .font(appDensity.font(.caption2)) |
| @@ -110,7 +110,7 @@ struct BatchResultRowView: View { | |||
| 110 | if let errorMessage = result.errorMessage { | 110 | if let errorMessage = result.errorMessage { |
| 111 | Text(errorMessage) | 111 | Text(errorMessage) |
| 112 | .font(appDensity.font(.caption2)) | 112 | .font(appDensity.font(.caption2)) |
| 113 | .foregroundStyle(result.status == .failed ? .red : .secondary) | 113 | .foregroundStyle(result.status == .failed ? Color(.statusCritical) : .secondary) |
| 114 | } | 114 | } |
| 115 | } | 115 | } |
| 116 | .frame(maxWidth: .infinity, alignment: .leading) | 116 | .frame(maxWidth: .infinity, alignment: .leading) |
| @@ -138,29 +138,29 @@ struct BatchResultRowView: View { | |||
| 138 | title: availabilityText, | 138 | title: availabilityText, |
| 139 | systemImage: "exclamationmark.circle", | 139 | systemImage: "exclamationmark.circle", |
| 140 | foregroundColor: .secondary, | 140 | foregroundColor: .secondary, |
| 141 | backgroundColor: Color(.systemGray5).opacity(0.55) | 141 | backgroundColor: Color(.appSurfaceElevated) |
| 142 | ) | 142 | ) |
| 143 | } | 143 | } |
| 144 | 144 | ||
| 145 | private var quickStatusBadge: AppStatusBadgeModel { | 145 | private var quickStatusBadge: AppStatusBadgeModel { |
| 146 | switch result.status { | 146 | switch result.status { |
| 147 | case .pending: | 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 | case .running: | 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 | case .completed: | 151 | case .completed: |
| 152 | if result.changeClassification == .critical || result.certificateWarningLevel == .critical || result.riskLevel == .high { | 152 | if result.changeClassification == .critical || result.certificateWarningLevel == .critical || result.riskLevel == .high { |
| 153 | return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: .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 | if result.changeClassification == .warning || result.changeSeverity == .medium || result.certificateWarningLevel == .warning { | 155 | if result.changeClassification == .warning || result.changeSeverity == .medium || result.certificateWarningLevel == .warning { |
| 156 | return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .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 | if result.quickStatus == "Changed" { | 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 | case .failed: | 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 | .background( | 168 | .background( |
| 169 | LinearGradient( | 169 | LinearGradient( |
| 170 | colors: [Color.black, Color(.systemGray6).opacity(0.12)], | 170 | colors: [Color(.appBackground), Color(.appSurface)], |
| 171 | startPoint: .top, | 171 | startPoint: .top, |
| 172 | endPoint: .bottom | 172 | endPoint: .bottom |
| 173 | ) | 173 | ) |
| @@ -302,7 +302,7 @@ struct ContentView: View { | |||
| 302 | .keyboardType(.URL) | 302 | .keyboardType(.URL) |
| 303 | .padding(.horizontal, 12) | 303 | .padding(.horizontal, 12) |
| 304 | .padding(.vertical, appDensity.metrics.controlVerticalPadding) | 304 | .padding(.vertical, appDensity.metrics.controlVerticalPadding) |
| 305 | .background(Color(.systemGray6)) | 305 | .background(Color(.appSurfaceElevated)) |
| 306 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 306 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 307 | .focused($focusedInputField, equals: .singleDomain) | 307 | .focused($focusedInputField, equals: .singleDomain) |
| 308 | .onSubmit { | 308 | .onSubmit { |
| @@ -320,6 +320,7 @@ struct ContentView: View { | |||
| 320 | .frame(minHeight: appDensity.metrics.controlMinHeight) | 320 | .frame(minHeight: appDensity.metrics.controlMinHeight) |
| 321 | } | 321 | } |
| 322 | .buttonStyle(.borderedProminent) | 322 | .buttonStyle(.borderedProminent) |
| 323 | .tint(Color(.accentFill)) | ||
| 323 | .disabled(viewModel.trimmedDomain.isEmpty) | 324 | .disabled(viewModel.trimmedDomain.isEmpty) |
| 324 | } else { | 325 | } else { |
| 325 | if FeatureAccessService.hasAccess(to: .batchOperations) { | 326 | if FeatureAccessService.hasAccess(to: .batchOperations) { |
| @@ -346,7 +347,7 @@ struct ContentView: View { | |||
| 346 | .lineLimit(4...10) | 347 | .lineLimit(4...10) |
| 347 | .padding(.horizontal, 12) | 348 | .padding(.horizontal, 12) |
| 348 | .padding(.vertical, appDensity.metrics.controlVerticalPadding) | 349 | .padding(.vertical, appDensity.metrics.controlVerticalPadding) |
| 349 | .background(Color(.systemGray6)) | 350 | .background(Color(.appSurfaceElevated)) |
| 350 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 351 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 351 | .focused($focusedInputField, equals: .bulkDomains) | 352 | .focused($focusedInputField, equals: .bulkDomains) |
| 352 | 353 | ||
| @@ -360,6 +361,7 @@ struct ContentView: View { | |||
| 360 | .frame(minHeight: appDensity.metrics.controlMinHeight) | 361 | .frame(minHeight: appDensity.metrics.controlMinHeight) |
| 361 | } | 362 | } |
| 362 | .buttonStyle(.borderedProminent) | 363 | .buttonStyle(.borderedProminent) |
| 364 | .tint(Color(.accentFill)) | ||
| 363 | .disabled(viewModel.bulkInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.batchLookupRunning) | 365 | .disabled(viewModel.bulkInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.batchLookupRunning) |
| 364 | } | 366 | } |
| 365 | } else { | 367 | } else { |
| @@ -549,7 +551,7 @@ struct ContentView: View { | |||
| 549 | } label: { | 551 | } label: { |
| 550 | Image(systemName: viewModel.isCurrentDomainSaved ? "bookmark.fill" : "bookmark") | 552 | Image(systemName: viewModel.isCurrentDomainSaved ? "bookmark.fill" : "bookmark") |
| 551 | .font(appDensity.font(.body, design: .default)) | 553 | .font(appDensity.font(.body, design: .default)) |
| 552 | .foregroundStyle(viewModel.isCurrentDomainSaved ? .yellow : .secondary) | 554 | .foregroundStyle(viewModel.isCurrentDomainSaved ? Color(.statusWarning) : .secondary) |
| 553 | } | 555 | } |
| 554 | Menu { | 556 | Menu { |
| 555 | Button("Export TXT") { | 557 | Button("Export TXT") { |
| @@ -671,7 +673,7 @@ struct ContentView: View { | |||
| 671 | .frame(maxWidth: .infinity, alignment: .leading) | 673 | .frame(maxWidth: .infinity, alignment: .leading) |
| 672 | .padding(.vertical, 8) | 674 | .padding(.vertical, 8) |
| 673 | .padding(.horizontal, 10) | 675 | .padding(.horizontal, 10) |
| 674 | .background(Color(.systemGray6).opacity(0.5)) | 676 | .background(Color(.appSurface)) |
| 675 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 677 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 676 | } | 678 | } |
| 677 | } | 679 | } |
| @@ -696,7 +698,7 @@ struct ContentView: View { | |||
| 696 | } | 698 | } |
| 697 | .frame(maxWidth: .infinity, alignment: .leading) | 699 | .frame(maxWidth: .infinity, alignment: .leading) |
| 698 | .padding(appDensity.metrics.cardPadding) | 700 | .padding(appDensity.metrics.cardPadding) |
| 699 | .background(Color(.systemGray6)) | 701 | .background(Color(.appSurfaceElevated)) |
| 700 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 702 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 701 | } | 703 | } |
| 702 | 704 | ||
| @@ -792,7 +794,7 @@ struct SummaryView: View { | |||
| 792 | .frame(minHeight: appDensity.metrics.rowMinHeight + 12, alignment: .topLeading) | 794 | .frame(minHeight: appDensity.metrics.rowMinHeight + 12, alignment: .topLeading) |
| 793 | .frame(maxWidth: .infinity, alignment: .leading) | 795 | .frame(maxWidth: .infinity, alignment: .leading) |
| 794 | .padding(appDensity.metrics.cardPadding) | 796 | .padding(appDensity.metrics.cardPadding) |
| 795 | .background(Color(.systemGray6).opacity(0.5)) | 797 | .background(Color(.appSurface)) |
| 796 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 798 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 797 | } | 799 | } |
| 798 | } | 800 | } |
| @@ -851,22 +853,22 @@ struct RiskSummaryCardView: View { | |||
| 851 | private var levelColor: Color { | 853 | private var levelColor: Color { |
| 852 | switch report.riskAssessment.level { | 854 | switch report.riskAssessment.level { |
| 853 | case .low: | 855 | case .low: |
| 854 | return .green | 856 | return Color(.statusPositive) |
| 855 | case .medium: | 857 | case .medium: |
| 856 | return .yellow | 858 | return Color(.statusWarning) |
| 857 | case .high: | 859 | case .high: |
| 858 | return .red | 860 | return Color(.statusCritical) |
| 859 | } | 861 | } |
| 860 | } | 862 | } |
| 861 | 863 | ||
| 862 | private func factorColor(_ impact: RiskImpact) -> Color { | 864 | private func factorColor(_ impact: RiskImpact) -> Color { |
| 863 | switch impact { | 865 | switch impact { |
| 864 | case .positive: | 866 | case .positive: |
| 865 | return .green | 867 | return Color(.statusPositive) |
| 866 | case .neutral: | 868 | case .neutral: |
| 867 | return .secondary | 869 | return .secondary |
| 868 | case .negative: | 870 | case .negative: |
| 869 | return .red | 871 | return Color(.statusCritical) |
| 870 | } | 872 | } |
| 871 | } | 873 | } |
| 872 | } | 874 | } |
| @@ -888,7 +890,7 @@ struct InsightsSummaryCardView: View { | |||
| 888 | HStack(alignment: .top, spacing: 8) { | 890 | HStack(alignment: .top, spacing: 8) { |
| 889 | Image(systemName: "chart.line.uptrend.xyaxis") | 891 | Image(systemName: "chart.line.uptrend.xyaxis") |
| 890 | .font(appDensity.font(.caption2)) | 892 | .font(appDensity.font(.caption2)) |
| 891 | .foregroundStyle(.cyan) | 893 | .foregroundStyle(Color(.statusInfo)) |
| 892 | .padding(.top, 2) | 894 | .padding(.top, 2) |
| 893 | Text(insight) | 895 | Text(insight) |
| 894 | .font(appDensity.font(.caption)) | 896 | .font(appDensity.font(.caption)) |
| @@ -995,13 +997,13 @@ struct LookupStatusBannerView: View { | |||
| 995 | private var color: Color { | 997 | private var color: Color { |
| 996 | switch resultSource { | 998 | switch resultSource { |
| 997 | case .live: | 999 | case .live: |
| 998 | return .green | 1000 | return Color(.statusPositive) |
| 999 | case .cached: | 1001 | case .cached: |
| 1000 | return .secondary | 1002 | return .secondary |
| 1001 | case .mixed: | 1003 | case .mixed: |
| 1002 | return .yellow | 1004 | return Color(.statusWarning) |
| 1003 | case .snapshot: | 1005 | case .snapshot: |
| 1004 | return .orange | 1006 | return Color(.statusWarning) |
| 1005 | } | 1007 | } |
| 1006 | } | 1008 | } |
| 1007 | 1009 | ||
| @@ -1029,7 +1031,7 @@ struct DomainChangeSummaryView: View { | |||
| 1029 | HStack { | 1031 | HStack { |
| 1030 | Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle") | 1032 | Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle") |
| 1031 | .font(appDensity.font(.caption)) | 1033 | .font(appDensity.font(.caption)) |
| 1032 | .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .green) | 1034 | .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : Color(.statusPositive)) |
| 1033 | Spacer() | 1035 | Spacer() |
| 1034 | Text(summary.severity.title.uppercased()) | 1036 | Text(summary.severity.title.uppercased()) |
| 1035 | .font(appDensity.font(.caption2)) | 1037 | .font(appDensity.font(.caption2)) |
| @@ -1079,13 +1081,13 @@ struct DomainChangeSummaryView: View { | |||
| 1079 | if let riskScoreDelta = summary.riskScoreDelta { | 1081 | if let riskScoreDelta = summary.riskScoreDelta { |
| 1080 | Text("Risk delta: \(riskScoreDelta >= 0 ? "+" : "")\(riskScoreDelta)") | 1082 | Text("Risk delta: \(riskScoreDelta >= 0 ? "+" : "")\(riskScoreDelta)") |
| 1081 | .font(appDensity.font(.caption2)) | 1083 | .font(appDensity.font(.caption2)) |
| 1082 | .foregroundStyle(riskScoreDelta > 0 ? .orange : .secondary) | 1084 | .foregroundStyle(riskScoreDelta > 0 ? Color(.statusWarning) : .secondary) |
| 1083 | } | 1085 | } |
| 1084 | 1086 | ||
| 1085 | if let contextNote = summary.contextNote { | 1087 | if let contextNote = summary.contextNote { |
| 1086 | Text(contextNote) | 1088 | Text(contextNote) |
| 1087 | .font(appDensity.font(.caption2)) | 1089 | .font(appDensity.font(.caption2)) |
| 1088 | .foregroundStyle(.orange) | 1090 | .foregroundStyle(Color(.statusWarning)) |
| 1089 | } | 1091 | } |
| 1090 | } | 1092 | } |
| 1091 | .padding(.top, 4) | 1093 | .padding(.top, 4) |
| @@ -1101,9 +1103,9 @@ struct DomainChangeSummaryView: View { | |||
| 1101 | case .low: | 1103 | case .low: |
| 1102 | return .secondary | 1104 | return .secondary |
| 1103 | case .medium: | 1105 | case .medium: |
| 1104 | return .yellow | 1106 | return Color(.statusWarning) |
| 1105 | case .high: | 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 | .padding(10) | 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 | .cornerRadius(8) | 1211 | .cornerRadius(8) |
| 1210 | } | 1212 | } |
| 1211 | } label: { | 1213 | } label: { |
| @@ -1225,7 +1227,7 @@ struct DomainDiffView: View { | |||
| 1225 | .overlay { | 1227 | .overlay { |
| 1226 | if highlightedSectionID == section.id { | 1228 | if highlightedSectionID == section.id { |
| 1227 | RoundedRectangle(cornerRadius: 12) | 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 | case .low: | 1260 | case .low: |
| 1259 | return .blue | 1261 | return .blue |
| 1260 | case .medium: | 1262 | case .medium: |
| 1261 | return .yellow | 1263 | return Color(.statusWarning) |
| 1262 | case .high: | 1264 | case .high: |
| 1263 | return .red | 1265 | return Color(.statusCritical) |
| 1264 | } | 1266 | } |
| 1265 | } | 1267 | } |
| 1266 | 1268 | ||
| @@ -1269,9 +1271,9 @@ struct DomainDiffView: View { | |||
| 1269 | case .low: | 1271 | case .low: |
| 1270 | return .blue | 1272 | return .blue |
| 1271 | case .medium: | 1273 | case .medium: |
| 1272 | return .yellow | 1274 | return Color(.statusWarning) |
| 1273 | case .high: | 1275 | case .high: |
| 1274 | return .red | 1276 | return Color(.statusCritical) |
| 1275 | } | 1277 | } |
| 1276 | } | 1278 | } |
| 1277 | 1279 | ||
| @@ -1337,7 +1339,7 @@ struct DomainSectionView: View { | |||
| 1337 | CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) { | 1339 | CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) { |
| 1338 | if let trackedDomain { | 1340 | if let trackedDomain { |
| 1339 | HStack(spacing: 8) { | 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 | Button { | 1343 | Button { |
| 1342 | onTogglePinned() | 1344 | onTogglePinned() |
| 1343 | } label: { | 1345 | } label: { |
| @@ -1638,7 +1640,7 @@ struct IntelligenceSectionView: View { | |||
| 1638 | if item.isEphemeral { | 1640 | if item.isEphemeral { |
| 1639 | Text("Ephemeral") | 1641 | Text("Ephemeral") |
| 1640 | .font(appDensity.font(.caption2)) | 1642 | .font(appDensity.font(.caption2)) |
| 1641 | .foregroundStyle(.yellow) | 1643 | .foregroundStyle(Color(.statusWarning)) |
| 1642 | } | 1644 | } |
| 1643 | } | 1645 | } |
| 1644 | Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x") | 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 | VStack(alignment: .leading, spacing: 8) { | 1669 | VStack(alignment: .leading, spacing: 8) { |
| 1668 | Text(title) | 1670 | Text(title) |
| 1669 | .font(appDensity.font(.subheadline, weight: .semibold)) | 1671 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 1670 | .foregroundStyle(.cyan) | 1672 | .foregroundStyle(Color(.statusInfo)) |
| 1671 | content() | 1673 | content() |
| 1672 | } | 1674 | } |
| 1673 | } | 1675 | } |
| @@ -1758,7 +1760,7 @@ struct SubdomainsSectionView: View { | |||
| 1758 | HStack { | 1760 | HStack { |
| 1759 | Text("\(group.label).*") | 1761 | Text("\(group.label).*") |
| 1760 | .font(appDensity.font(.caption)) | 1762 | .font(appDensity.font(.caption)) |
| 1761 | .foregroundStyle(.cyan) | 1763 | .foregroundStyle(Color(.statusInfo)) |
| 1762 | Spacer() | 1764 | Spacer() |
| 1763 | Text("\(group.subdomains.count)") | 1765 | Text("\(group.subdomains.count)") |
| 1764 | .font(appDensity.font(.caption2)) | 1766 | .font(appDensity.font(.caption2)) |
| @@ -1776,10 +1778,10 @@ struct SubdomainsSectionView: View { | |||
| 1776 | if row.isInteresting { | 1778 | if row.isInteresting { |
| 1777 | Text("Interesting") | 1779 | Text("Interesting") |
| 1778 | .font(.system(.caption2, design: .monospaced)) | 1780 | .font(.system(.caption2, design: .monospaced)) |
| 1779 | .foregroundStyle(.yellow) | 1781 | .foregroundStyle(Color(.statusWarning)) |
| 1780 | .padding(.horizontal, 8) | 1782 | .padding(.horizontal, 8) |
| 1781 | .padding(.vertical, 4) | 1783 | .padding(.vertical, 4) |
| 1782 | .background(Color.yellow.opacity(0.14)) | 1784 | .background(Color(.statusWarning).opacity(0.14)) |
| 1783 | .clipShape(Capsule()) | 1785 | .clipShape(Capsule()) |
| 1784 | } | 1786 | } |
| 1785 | } | 1787 | } |
| @@ -1883,7 +1885,7 @@ struct DNSSectionView: View { | |||
| 1883 | Text(section.title) | 1885 | Text(section.title) |
| 1884 | .font(.system(.subheadline, design: .monospaced)) | 1886 | .font(.system(.subheadline, design: .monospaced)) |
| 1885 | .fontWeight(.semibold) | 1887 | .fontWeight(.semibold) |
| 1886 | .foregroundStyle(.cyan) | 1888 | .foregroundStyle(Color(.statusInfo)) |
| 1887 | 1889 | ||
| 1888 | if let message = section.message { | 1890 | if let message = section.message { |
| 1889 | MessageRowView(text: message.text, isError: message.isError) | 1891 | MessageRowView(text: message.text, isError: message.isError) |
| @@ -1910,7 +1912,7 @@ struct DNSSectionView: View { | |||
| 1910 | Text("PTR") | 1912 | Text("PTR") |
| 1911 | .font(.system(.subheadline, design: .monospaced)) | 1913 | .font(.system(.subheadline, design: .monospaced)) |
| 1912 | .fontWeight(.semibold) | 1914 | .fontWeight(.semibold) |
| 1913 | .foregroundStyle(.cyan) | 1915 | .foregroundStyle(Color(.statusInfo)) |
| 1914 | SectionTrustMetadataView(provenance: ptrProvenance, confidence: nil) | 1916 | SectionTrustMetadataView(provenance: ptrProvenance, confidence: nil) |
| 1915 | MessageRowView(text: ptrMessage.text, isError: ptrMessage.isError) | 1917 | MessageRowView(text: ptrMessage.text, isError: ptrMessage.isError) |
| 1916 | } | 1918 | } |
| @@ -1920,7 +1922,7 @@ struct DNSSectionView: View { | |||
| 1920 | HStack { | 1922 | HStack { |
| 1921 | Text("History") | 1923 | Text("History") |
| 1922 | .font(appDensity.font(.subheadline, weight: .semibold)) | 1924 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 1923 | .foregroundStyle(.cyan) | 1925 | .foregroundStyle(Color(.statusInfo)) |
| 1924 | Spacer() | 1926 | Spacer() |
| 1925 | if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder { | 1927 | if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder { |
| 1926 | Button("Load") { | 1928 | Button("Load") { |
| @@ -1990,7 +1992,7 @@ struct WebSectionView: View { | |||
| 1990 | HStack { | 1992 | HStack { |
| 1991 | Text("TLS") | 1993 | Text("TLS") |
| 1992 | .font(appDensity.font(.subheadline, weight: .semibold)) | 1994 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 1993 | .foregroundStyle(.cyan) | 1995 | .foregroundStyle(Color(.statusInfo)) |
| 1994 | Spacer() | 1996 | Spacer() |
| 1995 | if !sslLoading { | 1997 | if !sslLoading { |
| 1996 | AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: sslInfo, error: sslError)) | 1998 | AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: sslInfo, error: sslError)) |
| @@ -2034,7 +2036,7 @@ struct WebSectionView: View { | |||
| 2034 | CardView { | 2036 | CardView { |
| 2035 | Text("Headers") | 2037 | Text("Headers") |
| 2036 | .font(appDensity.font(.subheadline, weight: .semibold)) | 2038 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2037 | .foregroundStyle(.cyan) | 2039 | .foregroundStyle(Color(.statusInfo)) |
| 2038 | SectionTrustMetadataView(provenance: httpProvenance, confidence: nil) | 2040 | SectionTrustMetadataView(provenance: httpProvenance, confidence: nil) |
| 2039 | if headersLoading { | 2041 | if headersLoading { |
| 2040 | ProgressView("Fetching headers…") | 2042 | ProgressView("Fetching headers…") |
| @@ -2052,7 +2054,7 @@ struct WebSectionView: View { | |||
| 2052 | HStack(alignment: .top, spacing: 4) { | 2054 | HStack(alignment: .top, spacing: 4) { |
| 2053 | Text(header.name + ":") | 2055 | Text(header.name + ":") |
| 2054 | .font(appDensity.font(.caption)) | 2056 | .font(appDensity.font(.caption)) |
| 2055 | .foregroundStyle(header.isSecurityHeader ? .yellow : .cyan) | 2057 | .foregroundStyle(header.isSecurityHeader ? Color(.statusWarning) : Color(.statusInfo)) |
| 2056 | Text(header.value) | 2058 | Text(header.value) |
| 2057 | .font(appDensity.font(.caption)) | 2059 | .font(appDensity.font(.caption)) |
| 2058 | .foregroundStyle(.primary) | 2060 | .foregroundStyle(.primary) |
| @@ -2067,7 +2069,7 @@ struct WebSectionView: View { | |||
| 2067 | HStack { | 2069 | HStack { |
| 2068 | Text("Redirects") | 2070 | Text("Redirects") |
| 2069 | .font(appDensity.font(.subheadline, weight: .semibold)) | 2071 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2070 | .foregroundStyle(.cyan) | 2072 | .foregroundStyle(Color(.statusInfo)) |
| 2071 | Spacer() | 2073 | Spacer() |
| 2072 | if let finalURL { | 2074 | if let finalURL { |
| 2073 | AppCopyButton(value: finalURL, label: "Copy redirect URL") | 2075 | AppCopyButton(value: finalURL, label: "Copy redirect URL") |
| @@ -2093,7 +2095,7 @@ struct WebSectionView: View { | |||
| 2093 | .frame(width: 16, alignment: .trailing) | 2095 | .frame(width: 16, alignment: .trailing) |
| 2094 | Text(redirect.statusCode) | 2096 | Text(redirect.statusCode) |
| 2095 | .font(appDensity.font(.caption)) | 2097 | .font(appDensity.font(.caption)) |
| 2096 | .foregroundStyle(.cyan) | 2098 | .foregroundStyle(Color(.statusInfo)) |
| 2097 | .frame(width: 36, alignment: .leading) | 2099 | .frame(width: 36, alignment: .leading) |
| 2098 | Text(redirect.url) | 2100 | Text(redirect.url) |
| 2099 | .font(appDensity.font(.caption)) | 2101 | .font(appDensity.font(.caption)) |
| @@ -2163,7 +2165,7 @@ struct EmailSectionView: View { | |||
| 2163 | HStack(spacing: 8) { | 2165 | HStack(spacing: 8) { |
| 2164 | Text(row.label) | 2166 | Text(row.label) |
| 2165 | .font(appDensity.font(.caption)) | 2167 | .font(appDensity.font(.caption)) |
| 2166 | .foregroundStyle(.cyan) | 2168 | .foregroundStyle(Color(.statusInfo)) |
| 2167 | .frame(width: 76, alignment: .leading) | 2169 | .frame(width: 76, alignment: .leading) |
| 2168 | AppStatusBadgeView(model: emailRowBadge(row)) | 2170 | AppStatusBadgeView(model: emailRowBadge(row)) |
| 2169 | } | 2171 | } |
| @@ -2186,13 +2188,13 @@ struct EmailSectionView: View { | |||
| 2186 | private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel { | 2188 | private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel { |
| 2187 | switch row.statusTone { | 2189 | switch row.statusTone { |
| 2188 | case .success: | 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 | case .warning: | 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 | case .failure: | 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 | case .primary, .secondary: | 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 | CardView { | 2229 | CardView { |
| 2228 | Text("Reachability") | 2230 | Text("Reachability") |
| 2229 | .font(appDensity.font(.subheadline, weight: .semibold)) | 2231 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2230 | .foregroundStyle(.cyan) | 2232 | .foregroundStyle(Color(.statusInfo)) |
| 2231 | SectionTrustMetadataView(provenance: reachabilityProvenance, confidence: nil) | 2233 | SectionTrustMetadataView(provenance: reachabilityProvenance, confidence: nil) |
| 2232 | if reachabilityLoading { | 2234 | if reachabilityLoading { |
| 2233 | ProgressView("Checking ports…") | 2235 | ProgressView("Checking ports…") |
| @@ -2252,7 +2254,7 @@ struct NetworkSectionView: View { | |||
| 2252 | CardView(allowsHorizontalScroll: false) { | 2254 | CardView(allowsHorizontalScroll: false) { |
| 2253 | Text("Location") | 2255 | Text("Location") |
| 2254 | .font(appDensity.font(.subheadline, weight: .semibold)) | 2256 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2255 | .foregroundStyle(.cyan) | 2257 | .foregroundStyle(Color(.statusInfo)) |
| 2256 | SectionTrustMetadataView(provenance: geolocationProvenance, confidence: geolocationConfidence) | 2258 | SectionTrustMetadataView(provenance: geolocationProvenance, confidence: geolocationConfidence) |
| 2257 | if geolocationLoading { | 2259 | if geolocationLoading { |
| 2258 | ProgressView("Looking up location…") | 2260 | ProgressView("Looking up location…") |
| @@ -2284,13 +2286,13 @@ struct NetworkSectionView: View { | |||
| 2284 | CardView(allowsHorizontalScroll: false) { | 2286 | CardView(allowsHorizontalScroll: false) { |
| 2285 | Text("Port Scan") | 2287 | Text("Port Scan") |
| 2286 | .font(appDensity.font(.subheadline, weight: .semibold)) | 2288 | .font(appDensity.font(.subheadline, weight: .semibold)) |
| 2287 | .foregroundStyle(.cyan) | 2289 | .foregroundStyle(Color(.statusInfo)) |
| 2288 | SectionTrustMetadataView(provenance: portScanProvenance, confidence: nil) | 2290 | SectionTrustMetadataView(provenance: portScanProvenance, confidence: nil) |
| 2289 | 2291 | ||
| 2290 | if isCloudflareProxied { | 2292 | if isCloudflareProxied { |
| 2291 | Text("Domain is behind Cloudflare's proxy. Results reflect the edge, not the origin.") | 2293 | Text("Domain is behind Cloudflare's proxy. Results reflect the edge, not the origin.") |
| 2292 | .font(appDensity.font(.caption2)) | 2294 | .font(appDensity.font(.caption2)) |
| 2293 | .foregroundStyle(.orange) | 2295 | .foregroundStyle(Color(.statusWarning)) |
| 2294 | .fixedSize(horizontal: false, vertical: true) | 2296 | .fixedSize(horizontal: false, vertical: true) |
| 2295 | } | 2297 | } |
| 2296 | 2298 | ||
| @@ -2314,7 +2316,7 @@ struct NetworkSectionView: View { | |||
| 2314 | .autocorrectionDisabled() | 2316 | .autocorrectionDisabled() |
| 2315 | .keyboardType(.numberPad) | 2317 | .keyboardType(.numberPad) |
| 2316 | .padding(10) | 2318 | .padding(10) |
| 2317 | .background(Color(.systemGray6).opacity(0.5)) | 2319 | .background(Color(.appSurface)) |
| 2318 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 2320 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 2319 | 2321 | ||
| 2320 | Button("Scan") { | 2322 | Button("Scan") { |
| @@ -2322,6 +2324,7 @@ struct NetworkSectionView: View { | |||
| 2322 | onScanCustomPorts() | 2324 | onScanCustomPorts() |
| 2323 | } | 2325 | } |
| 2324 | .buttonStyle(.borderedProminent) | 2326 | .buttonStyle(.borderedProminent) |
| 2327 | .tint(Color(.accentFill)) | ||
| 2325 | .disabled(customPortScanLoading) | 2328 | .disabled(customPortScanLoading) |
| 2326 | 2329 | ||
| 2327 | if customPortScanLoading { | 2330 | if customPortScanLoading { |
| @@ -2344,13 +2347,13 @@ struct NetworkSectionView: View { | |||
| 2344 | private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel { | 2347 | private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel { |
| 2345 | switch row.statusTone { | 2348 | switch row.statusTone { |
| 2346 | case .success: | 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 | case .warning: | 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 | case .failure: | 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 | case .primary, .secondary: | 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 | private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel { | 2398 | private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel { |
| 2396 | switch row.statusTone { | 2399 | switch row.statusTone { |
| 2397 | case .success: | 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 | case .warning: | 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 | case .failure: | 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 | case .primary, .secondary: | 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 | var body: some View { | 2416 | var body: some View { |
| 2414 | Text(title) | 2417 | Text(title) |
| 2415 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) | 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 | .frame(maxWidth: .infinity, alignment: .leading) | 2446 | .frame(maxWidth: .infinity, alignment: .leading) |
| 2444 | .padding(appDensity.metrics.cardPadding) | 2447 | .padding(appDensity.metrics.cardPadding) |
| 2445 | .background(Color(.systemGray6).opacity(0.5)) | 2448 | .background(Color(.appSurface)) |
| 2446 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 2449 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 2447 | } | 2450 | } |
| 2448 | 2451 | ||
| @@ -2484,7 +2487,7 @@ struct MessageRowView: View { | |||
| 2484 | var body: some View { | 2487 | var body: some View { |
| 2485 | Label(text, systemImage: isError ? "exclamationmark.triangle.fill" : "info.circle") | 2488 | Label(text, systemImage: isError ? "exclamationmark.triangle.fill" : "info.circle") |
| 2486 | .font(appDensity.font(.caption)) | 2489 | .font(appDensity.font(.caption)) |
| 2487 | .foregroundStyle(isError ? .red : .secondary) | 2490 | .foregroundStyle(isError ? Color(.statusCritical) : .secondary) |
| 2488 | .lineLimit(nil) | 2491 | .lineLimit(nil) |
| 2489 | .fixedSize(horizontal: false, vertical: true) | 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 | enum ResultColors { | 2587 | enum ResultColors { |
| 2580 | static func color(for tone: ResultTone) -> Color { | 2588 | static func color(for tone: ResultTone) -> Color { |
| 2581 | switch tone { | 2589 | switch tone { |
| @@ -2584,11 +2592,11 @@ enum ResultColors { | |||
| 2584 | case .secondary: | 2592 | case .secondary: |
| 2585 | return .secondary | 2593 | return .secondary |
| 2586 | case .success: | 2594 | case .success: |
| 2587 | return .green | 2595 | return Color(.statusPositive) |
| 2588 | case .warning: | 2596 | case .warning: |
| 2589 | return .yellow | 2597 | return Color(.statusWarning) |
| 2590 | case .failure: | 2598 | case .failure: |
| 2591 | return .red | 2599 | return Color(.statusCritical) |
| 2592 | } | 2600 | } |
| 2593 | } | 2601 | } |
| 2594 | } | 2602 | } |
| @@ -2654,7 +2662,7 @@ struct SettingsView: View { | |||
| 2654 | if let errorMessage = purchaseService.errorMessage { | 2662 | if let errorMessage = purchaseService.errorMessage { |
| 2655 | Text(errorMessage) | 2663 | Text(errorMessage) |
| 2656 | .font(appDensity.font(.caption, design: .default)) | 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 | if let customResolverError { | 2797 | if let customResolverError { |
| 2790 | Text(customResolverError) | 2798 | Text(customResolverError) |
| 2791 | .font(appDensity.font(.caption, design: .default)) | 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 | if let lastErrorMessage = cloudSyncService.lastErrorMessage { | 2879 | if let lastErrorMessage = cloudSyncService.lastErrorMessage { |
| 2872 | Text(lastErrorMessage) | 2880 | Text(lastErrorMessage) |
| 2873 | .font(appDensity.font(.caption, design: .default)) | 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 | Spacer() | 2972 | Spacer() |
| 2965 | Text("\(log.statusCode)") | 2973 | Text("\(log.statusCode)") |
| 2966 | .font(appDensity.font(.caption, design: .default)) | 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 | Text(log.timestamp.formatted(date: .abbreviated, time: .standard)) | 2978 | Text(log.timestamp.formatted(date: .abbreviated, time: .standard)) |
| @@ -3744,8 +3752,8 @@ extension ChangeImpactClassification { | |||
| 3744 | var color: Color { | 3752 | var color: Color { |
| 3745 | switch self { | 3753 | switch self { |
| 3746 | case .informational: return .secondary | 3754 | case .informational: return .secondary |
| 3747 | case .warning: return .yellow | 3755 | case .warning: return Color(.statusWarning) |
| 3748 | case .critical: return .red | 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 | showsCardBackground: false | 22 | showsCardBackground: false |
| 23 | ) | 23 | ) |
| 24 | } | 24 | } |
| 25 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 25 | .listRowBackground(Color(.appSurface)) |
| 26 | } else { | 26 | } else { |
| 27 | Section { | 27 | Section { |
| 28 | LazyVGrid(columns: summaryColumns, spacing: 10) { | 28 | LazyVGrid(columns: summaryColumns, spacing: 10) { |
| 29 | summaryCard(title: "Total Domains", value: viewModel.portfolioDashboardData.snapshot.totalDomains, filter: .all, tint: .cyan) | 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: .green) | 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: .yellow) | 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: .red) | 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: .orange) | 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: .pink) | 34 | summaryCard(title: "Unreachable", value: viewModel.portfolioDashboardData.snapshot.unreachableCount, filter: .unreachable, tint: Color(.statusCritical)) |
| 35 | } | 35 | } |
| 36 | .padding(.vertical, 4) | 36 | .padding(.vertical, 4) |
| 37 | } | 37 | } |
| 38 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 38 | .listRowBackground(Color(.appSurface)) |
| 39 | 39 | ||
| 40 | Section("Quick Filters") { | 40 | Section("Quick Filters") { |
| 41 | ScrollView(.horizontal, showsIndicators: false) { | 41 | ScrollView(.horizontal, showsIndicators: false) { |
| @@ -46,10 +46,10 @@ struct DashboardView: View { | |||
| 46 | } label: { | 46 | } label: { |
| 47 | Text(filter.title) | 47 | Text(filter.title) |
| 48 | .font(appDensity.font(.caption, weight: .semibold)) | 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 | .padding(.horizontal, 12) | 50 | .padding(.horizontal, 12) |
| 51 | .padding(.vertical, 8) | 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 | .clipShape(Capsule()) | 53 | .clipShape(Capsule()) |
| 54 | } | 54 | } |
| 55 | .buttonStyle(.plain) | 55 | .buttonStyle(.plain) |
| @@ -58,7 +58,7 @@ struct DashboardView: View { | |||
| 58 | .padding(.vertical, 4) | 58 | .padding(.vertical, 4) |
| 59 | } | 59 | } |
| 60 | } | 60 | } |
| 61 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 61 | .listRowBackground(Color(.appSurface)) |
| 62 | 62 | ||
| 63 | Section("Recent Activity") { | 63 | Section("Recent Activity") { |
| 64 | if viewModel.filteredPortfolioRecentActivity.isEmpty { | 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 | Section("Attention Required") { | 81 | Section("Attention Required") { |
| 82 | if viewModel.filteredPortfolioAttentionRequired.isEmpty { | 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 | Section("Expiring Soon") { | 99 | Section("Expiring Soon") { |
| 100 | if viewModel.filteredPortfolioExpiringSoon.isEmpty { | 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 | Section("Portfolio List") { | 115 | Section("Portfolio List") { |
| 116 | if viewModel.filteredPortfolioGroups.isEmpty { | 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 | .scrollContentBackground(.hidden) | 156 | .scrollContentBackground(.hidden) |
| 157 | .background(Color.black) | 157 | .background(Color(.appBackground)) |
| 158 | .navigationTitle("Dashboard") | 158 | .navigationTitle("Dashboard") |
| 159 | .searchable(text: $viewModel.dashboardSearchText, prompt: "Search portfolio") | 159 | .searchable(text: $viewModel.dashboardSearchText, prompt: "Search portfolio") |
| 160 | .toolbar { | 160 | .toolbar { |
| @@ -196,7 +196,7 @@ struct DashboardView: View { | |||
| 196 | .foregroundStyle(.secondary) | 196 | .foregroundStyle(.secondary) |
| 197 | Text("\(value)") | 197 | Text("\(value)") |
| 198 | .font(.system(size: 28, weight: .bold, design: .rounded)) | 198 | .font(.system(size: 28, weight: .bold, design: .rounded)) |
| 199 | .foregroundStyle(.white) | 199 | .foregroundStyle(.primary) |
| 200 | HStack { | 200 | HStack { |
| 201 | Circle() | 201 | Circle() |
| 202 | .fill(tint) | 202 | .fill(tint) |
| @@ -218,13 +218,13 @@ struct DashboardView: View { | |||
| 218 | if viewModel.dashboardFilter == filter { | 218 | if viewModel.dashboardFilter == filter { |
| 219 | return AnyShapeStyle( | 219 | return AnyShapeStyle( |
| 220 | LinearGradient( | 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 | startPoint: .topLeading, | 222 | startPoint: .topLeading, |
| 223 | endPoint: .bottomTrailing | 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 | private func dashboardEmptyRow(_ message: String) -> some View { | 230 | private func dashboardEmptyRow(_ message: String) -> some View { |
| @@ -243,15 +243,15 @@ struct DashboardView: View { | |||
| 243 | 243 | ||
| 244 | if criticalCount > 0 { | 244 | if criticalCount > 0 { |
| 245 | title = "\(criticalCount) critical" | 245 | title = "\(criticalCount) critical" |
| 246 | color = .red | 246 | color = Color(.statusCritical) |
| 247 | systemImage = "exclamationmark.octagon.fill" | 247 | systemImage = "exclamationmark.octagon.fill" |
| 248 | } else if warningCount > 0 { | 248 | } else if warningCount > 0 { |
| 249 | title = "\(warningCount) warning" | 249 | title = "\(warningCount) warning" |
| 250 | color = .yellow | 250 | color = Color(.statusWarning) |
| 251 | systemImage = "exclamationmark.triangle.fill" | 251 | systemImage = "exclamationmark.triangle.fill" |
| 252 | } else { | 252 | } else { |
| 253 | title = "Healthy" | 253 | title = "Healthy" |
| 254 | color = .green | 254 | color = Color(.statusPositive) |
| 255 | systemImage = "checkmark.circle.fill" | 255 | systemImage = "checkmark.circle.fill" |
| 256 | } | 256 | } |
| 257 | 257 | ||
| @@ -297,11 +297,11 @@ private struct PortfolioActivityRow: View { | |||
| 297 | private var iconColor: Color { | 297 | private var iconColor: Color { |
| 298 | switch item.health { | 298 | switch item.health { |
| 299 | case .healthy: | 299 | case .healthy: |
| 300 | return .cyan | 300 | return Color(.statusInfo) |
| 301 | case .warning: | 301 | case .warning: |
| 302 | return .yellow | 302 | return Color(.statusWarning) |
| 303 | case .critical: | 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 | private var badgeModel: AppStatusBadgeModel { | 332 | private var badgeModel: AppStatusBadgeModel { |
| 333 | switch item.health { | 333 | switch item.health { |
| 334 | case .healthy: | 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 | case .warning: | 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 | case .critical: | 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 | private var badgeModel: AppStatusBadgeModel { | 371 | private var badgeModel: AppStatusBadgeModel { |
| 372 | switch state.certificateExpiryState { | 372 | switch state.certificateExpiryState { |
| 373 | case .none: | 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 | case .warning: | 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 | case .critical: | 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 | showsCardBackground: false | 63 | showsCardBackground: false |
| 64 | ) | 64 | ) |
| 65 | } | 65 | } |
| 66 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 66 | .listRowBackground(Color(.appSurface)) |
| 67 | } | 67 | } |
| 68 | } | 68 | } |
| 69 | .scrollContentBackground(.hidden) | 69 | .scrollContentBackground(.hidden) |
| 70 | .background(Color.black) | 70 | .background(Color(.appBackground)) |
| 71 | .navigationTitle("Compare Domains") | 71 | .navigationTitle("Compare Domains") |
| 72 | } | 72 | } |
| 73 | 73 | ||
DomainDig/DomainDig/AuditViews.swift +24 −24
| @@ -27,7 +27,7 @@ struct AuditListView: View { | |||
| 27 | showsCardBackground: false | 27 | showsCardBackground: false |
| 28 | ) | 28 | ) |
| 29 | } | 29 | } |
| 30 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 30 | .listRowBackground(Color(.appSurface)) |
| 31 | } else { | 31 | } else { |
| 32 | Section("Audit Domains") { | 32 | Section("Audit Domains") { |
| 33 | ForEach(groupedSessions, id: \.domain) { group in | 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 | .scrollContentBackground(.hidden) | 71 | .scrollContentBackground(.hidden) |
| 72 | .background(Color.black) | 72 | .background(Color(.appBackground)) |
| 73 | .navigationTitle("Audit Mode") | 73 | .navigationTitle("Audit Mode") |
| 74 | .toolbar { | 74 | .toolbar { |
| 75 | if !viewModel.searchedDomain.isEmpty { | 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 | Section("Trend") { | 137 | Section("Trend") { |
| 138 | ForEach(viewModel.auditTimeline(for: domain)) { point in | 138 | ForEach(viewModel.auditTimeline(for: domain)) { point in |
| @@ -152,10 +152,10 @@ struct AuditDomainTimelineView: View { | |||
| 152 | .padding(.vertical, 2) | 152 | .padding(.vertical, 2) |
| 153 | } | 153 | } |
| 154 | } | 154 | } |
| 155 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 155 | .listRowBackground(Color(.appSurface)) |
| 156 | } | 156 | } |
| 157 | .scrollContentBackground(.hidden) | 157 | .scrollContentBackground(.hidden) |
| 158 | .background(Color.black) | 158 | .background(Color(.appBackground)) |
| 159 | .navigationTitle(domain) | 159 | .navigationTitle(domain) |
| 160 | .toolbar { | 160 | .toolbar { |
| 161 | ToolbarItem(placement: .topBarTrailing) { | 161 | ToolbarItem(placement: .topBarTrailing) { |
| @@ -218,7 +218,7 @@ struct AuditSessionDetailView: View { | |||
| 218 | .foregroundStyle(.secondary) | 218 | .foregroundStyle(.secondary) |
| 219 | } | 219 | } |
| 220 | } | 220 | } |
| 221 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 221 | .listRowBackground(Color(.appSurface)) |
| 222 | 222 | ||
| 223 | Section("Audit Summary") { | 223 | Section("Audit Summary") { |
| 224 | LabeledContent("Findings", value: "\(session.findings.count)") | 224 | LabeledContent("Findings", value: "\(session.findings.count)") |
| @@ -228,7 +228,7 @@ struct AuditSessionDetailView: View { | |||
| 228 | LabeledContent("Highest Severity", value: severity.title) | 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 | Section("Reviewer Notes") { | 233 | Section("Reviewer Notes") { |
| 234 | TextEditor(text: $notesDraft) | 234 | TextEditor(text: $notesDraft) |
| @@ -240,7 +240,7 @@ struct AuditSessionDetailView: View { | |||
| 240 | viewModel.updateAuditNotes(notesDraft, sessionID: session.id) | 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 | Section("Checklist") { | 245 | Section("Checklist") { |
| 246 | ForEach(session.checklist) { item in | 246 | ForEach(session.checklist) { item in |
| @@ -249,7 +249,7 @@ struct AuditSessionDetailView: View { | |||
| 249 | } label: { | 249 | } label: { |
| 250 | HStack(alignment: .top, spacing: 10) { | 250 | HStack(alignment: .top, spacing: 10) { |
| 251 | Image(systemName: item.isComplete ? "checkmark.circle.fill" : "circle") | 251 | Image(systemName: item.isComplete ? "checkmark.circle.fill" : "circle") |
| 252 | .foregroundStyle(item.isComplete ? Color.green : .secondary) | 252 | .foregroundStyle(item.isComplete ? Color(.statusPositive) : .secondary) |
| 253 | VStack(alignment: .leading, spacing: 4) { | 253 | VStack(alignment: .leading, spacing: 4) { |
| 254 | Text(item.title) | 254 | Text(item.title) |
| 255 | .font(appDensity.font(.callout)) | 255 | .font(appDensity.font(.callout)) |
| @@ -264,7 +264,7 @@ struct AuditSessionDetailView: View { | |||
| 264 | .buttonStyle(.plain) | 264 | .buttonStyle(.plain) |
| 265 | } | 265 | } |
| 266 | } | 266 | } |
| 267 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 267 | .listRowBackground(Color(.appSurface)) |
| 268 | 268 | ||
| 269 | Section("Findings") { | 269 | Section("Findings") { |
| 270 | if session.findings.isEmpty { | 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 | Section("Evidence Snapshot") { | 306 | Section("Evidence Snapshot") { |
| 307 | LabeledContent("Availability", value: availabilityTitle(session.evidence.report.availability)) | 307 | LabeledContent("Availability", value: availabilityTitle(session.evidence.report.availability)) |
| @@ -311,7 +311,7 @@ struct AuditSessionDetailView: View { | |||
| 311 | LabeledContent("Reachability", value: session.evidence.report.network.reachabilitySummary) | 311 | LabeledContent("Reachability", value: session.evidence.report.network.reachabilitySummary) |
| 312 | LabeledContent("Registrar", value: session.evidence.report.ownership?.registrar ?? "Unavailable") | 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 | if !session.evidence.historicalContext.isEmpty { | 316 | if !session.evidence.historicalContext.isEmpty { |
| 317 | Section("Historical Context") { | 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 | Section("Audit Timeline") { | 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 | .scrollContentBackground(.hidden) | 350 | .scrollContentBackground(.hidden) |
| 351 | .background(Color.black) | 351 | .background(Color(.appBackground)) |
| 352 | .navigationTitle("Audit Session") | 352 | .navigationTitle("Audit Session") |
| 353 | .toolbar { | 353 | .toolbar { |
| 354 | ToolbarItemGroup(placement: .topBarTrailing) { | 354 | ToolbarItemGroup(placement: .topBarTrailing) { |
| @@ -398,7 +398,7 @@ struct AuditSessionDetailView: View { | |||
| 398 | } | 398 | } |
| 399 | } else { | 399 | } else { |
| 400 | ContentUnavailableView("Audit Session Missing", systemImage: "exclamationmark.triangle") | 400 | ContentUnavailableView("Audit Session Missing", systemImage: "exclamationmark.triangle") |
| 401 | .background(Color.black) | 401 | .background(Color(.appBackground)) |
| 402 | } | 402 | } |
| 403 | } | 403 | } |
| 404 | .preferredColorScheme(.dark) | 404 | .preferredColorScheme(.dark) |
| @@ -477,7 +477,7 @@ private struct AuditFindingEditorView: View { | |||
| 477 | Text(area.title) | 477 | Text(area.title) |
| 478 | Spacer() | 478 | Spacer() |
| 479 | Image(systemName: selectedAreas.contains(area) ? "checkmark.circle.fill" : "circle") | 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 | .buttonStyle(.plain) | 483 | .buttonStyle(.plain) |
| @@ -528,11 +528,11 @@ private func auditStatusBadge(_ status: AuditStatus) -> some View { | |||
| 528 | let model: AppStatusBadgeModel | 528 | let model: AppStatusBadgeModel |
| 529 | switch status { | 529 | switch status { |
| 530 | case .draft: | 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 | case .inReview: | 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 | case .complete: | 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 | return AppStatusBadgeView(model: model) | 537 | return AppStatusBadgeView(model: model) |
| 538 | } | 538 | } |
| @@ -543,11 +543,11 @@ private func findingSeverityBadge(_ severity: AuditFindingSeverity) -> some View | |||
| 543 | case .informational: | 543 | case .informational: |
| 544 | color = .secondary | 544 | color = .secondary |
| 545 | case .low: | 545 | case .low: |
| 546 | color = .green | 546 | color = Color(.statusPositive) |
| 547 | case .medium: | 547 | case .medium: |
| 548 | color = .yellow | 548 | color = Color(.statusWarning) |
| 549 | case .high: | 549 | case .high: |
| 550 | color = .red | 550 | color = Color(.statusCritical) |
| 551 | } | 551 | } |
| 552 | return AppStatusBadgeView( | 552 | return AppStatusBadgeView( |
| 553 | model: .init( | 553 | model: .init( |
DomainDig/DomainDigUI.swift +21 −21
| @@ -92,48 +92,48 @@ enum AppStatusFactory { | |||
| 92 | static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel { | 92 | static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel { |
| 93 | switch status { | 93 | switch status { |
| 94 | case .available: | 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 | case .registered: | 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 | case .unknown, .none: | 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 | static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel { | 103 | static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel { |
| 104 | if error != nil || sslInfo == nil { | 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 | if let sslInfo, sslInfo.daysUntilExpiry <= 14 { | 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 | static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel { | 113 | static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel { |
| 114 | guard error == nil, let result else { | 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 | let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count | 118 | let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count |
| 119 | switch foundCount { | 119 | switch foundCount { |
| 120 | case 3: | 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 | case 1, 2: | 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 | default: | 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 | static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel { | 129 | static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel { |
| 130 | guard let summary else { | 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 | if summary.hasChanges { | 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 | } label: { | 184 | } label: { |
| 185 | Image(systemName: didCopy ? "checkmark" : "doc.on.doc") | 185 | Image(systemName: didCopy ? "checkmark" : "doc.on.doc") |
| 186 | .font(appDensity.font(.caption)) | 186 | .font(appDensity.font(.caption)) |
| 187 | .foregroundStyle(didCopy ? Color.green : .secondary) | 187 | .foregroundStyle(didCopy ? Color(.statusPositive) : .secondary) |
| 188 | .frame(width: 30, height: 30) | 188 | .frame(width: 30, height: 30) |
| 189 | .background(Color(.systemGray5).opacity(0.35)) | 189 | .background(Color(.appSurfaceElevated)) |
| 190 | .clipShape(RoundedRectangle(cornerRadius: 8)) | 190 | .clipShape(RoundedRectangle(cornerRadius: 8)) |
| 191 | } | 191 | } |
| 192 | .buttonStyle(.plain) | 192 | .buttonStyle(.plain) |
| @@ -264,11 +264,11 @@ struct EmptyStateCardView: View { | |||
| 264 | 264 | ||
| 265 | Text(suggestion) | 265 | Text(suggestion) |
| 266 | .font(appDensity.font(.caption)) | 266 | .font(appDensity.font(.caption)) |
| 267 | .foregroundStyle(.cyan) | 267 | .foregroundStyle(Color(.statusInfo)) |
| 268 | } | 268 | } |
| 269 | .frame(maxWidth: .infinity, alignment: .leading) | 269 | .frame(maxWidth: .infinity, alignment: .leading) |
| 270 | .padding(appDensity.metrics.cardPadding) | 270 | .padding(appDensity.metrics.cardPadding) |
| 271 | .background(showsCardBackground ? Color(.systemGray6).opacity(0.45) : Color.clear) | 271 | .background(showsCardBackground ? Color(.appSurface) : Color.clear) |
| 272 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 272 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 273 | } | 273 | } |
| 274 | } | 274 | } |
| @@ -307,7 +307,7 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View { | |||
| 307 | VStack(alignment: .leading, spacing: 3) { | 307 | VStack(alignment: .leading, spacing: 3) { |
| 308 | Text(title) | 308 | Text(title) |
| 309 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) | 309 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) |
| 310 | .foregroundStyle(.white) | 310 | .foregroundStyle(.primary) |
| 311 | if let subtitle { | 311 | if let subtitle { |
| 312 | Text(subtitle) | 312 | Text(subtitle) |
| 313 | .font(appDensity.font(.caption)) | 313 | .font(appDensity.font(.caption)) |
| @@ -346,7 +346,7 @@ struct TagChipRowView: View { | |||
| 346 | .font(.caption) | 346 | .font(.caption) |
| 347 | .padding(.horizontal, 10) | 347 | .padding(.horizontal, 10) |
| 348 | .padding(.vertical, 5) | 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 | .font(.caption) | 380 | .font(.caption) |
| 381 | .padding(.horizontal, 10) | 381 | .padding(.horizontal, 10) |
| 382 | .padding(.vertical, 5) | 382 | .padding(.vertical, 5) |
| 383 | .background(isSelected ? Color.cyan.opacity(0.3) : Color(.systemGray5).opacity(0.6), in: Capsule()) | 383 | .background(isSelected ? Color(.statusInfo).opacity(0.3) : Color(.appSurfaceElevated), in: Capsule()) |
| 384 | .foregroundStyle(isSelected ? Color.cyan : Color.primary) | 384 | .foregroundStyle(isSelected ? Color(.statusInfo) : Color.primary) |
| 385 | } | 385 | } |
| 386 | .buttonStyle(.plain) | 386 | .buttonStyle(.plain) |
| 387 | } | 387 | } |
DomainDig/HistoryView.swift +9 −9
| @@ -25,7 +25,7 @@ struct HistoryView: View { | |||
| 25 | systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90", | 25 | systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90", |
| 26 | showsCardBackground: false | 26 | showsCardBackground: false |
| 27 | ) | 27 | ) |
| 28 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 28 | .listRowBackground(Color(.appSurface)) |
| 29 | } else { | 29 | } else { |
| 30 | Section("Domains") { | 30 | Section("Domains") { |
| 31 | ForEach(domainSummaries, id: \.domain) { item in | 31 | ForEach(domainSummaries, id: \.domain) { item in |
| @@ -55,21 +55,21 @@ struct HistoryView: View { | |||
| 55 | model: .init( | 55 | model: .init( |
| 56 | title: severity.title, | 56 | title: severity.title, |
| 57 | systemImage: "arrow.triangle.2.circlepath", | 57 | systemImage: "arrow.triangle.2.circlepath", |
| 58 | foregroundColor: severity == .high ? .red : .yellow, | 58 | foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), |
| 59 | backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16) | 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 | .scrollContentBackground(.hidden) | 71 | .scrollContentBackground(.hidden) |
| 72 | .background(Color.black) | 72 | .background(Color(.appBackground)) |
| 73 | .navigationTitle("History") | 73 | .navigationTitle("History") |
| 74 | .searchable(text: $viewModel.timelineDomainFilter, prompt: "Search domains") | 74 | .searchable(text: $viewModel.timelineDomainFilter, prompt: "Search domains") |
| 75 | .toolbar { | 75 | .toolbar { |
| @@ -321,7 +321,7 @@ struct HistoryDetailView: View { | |||
| 321 | .padding(.horizontal) | 321 | .padding(.horizontal) |
| 322 | .padding(.bottom, 32) | 322 | .padding(.bottom, 32) |
| 323 | } | 323 | } |
| 324 | .background(Color.black) | 324 | .background(Color(.appBackground)) |
| 325 | .navigationTitle(entry.domain) | 325 | .navigationTitle(entry.domain) |
| 326 | .toolbar { | 326 | .toolbar { |
| 327 | ToolbarItemGroup(placement: .topBarTrailing) { | 327 | ToolbarItemGroup(placement: .topBarTrailing) { |
| @@ -390,12 +390,12 @@ struct HistoryDetailView: View { | |||
| 390 | if let mismatchNote = viewModel.resolverMismatchNote(for: entry) { | 390 | if let mismatchNote = viewModel.resolverMismatchNote(for: entry) { |
| 391 | Text(mismatchNote) | 391 | Text(mismatchNote) |
| 392 | .font(appDensity.font(.caption2)) | 392 | .font(appDensity.font(.caption2)) |
| 393 | .foregroundStyle(.orange) | 393 | .foregroundStyle(Color(.statusWarning)) |
| 394 | } | 394 | } |
| 395 | if entry.isPartialSnapshot { | 395 | if entry.isPartialSnapshot { |
| 396 | Text("Partial snapshot: \(entry.validationIssues.joined(separator: " | "))") | 396 | Text("Partial snapshot: \(entry.validationIssues.joined(separator: " | "))") |
| 397 | .font(appDensity.font(.caption2)) | 397 | .font(appDensity.font(.caption2)) |
| 398 | .foregroundStyle(.yellow) | 398 | .foregroundStyle(Color(.statusWarning)) |
| 399 | } | 399 | } |
| 400 | if let note = entry.note, !note.isEmpty { | 400 | if let note = entry.note, !note.isEmpty { |
| 401 | Text(note) | 401 | Text(note) |
| @@ -406,7 +406,7 @@ struct HistoryDetailView: View { | |||
| 406 | .foregroundStyle(.secondary) | 406 | .foregroundStyle(.secondary) |
| 407 | .padding(8) | 407 | .padding(8) |
| 408 | .frame(maxWidth: .infinity, alignment: .leading) | 408 | .frame(maxWidth: .infinity, alignment: .leading) |
| 409 | .background(Color(.systemGray6).opacity(0.3)) | 409 | .background(Color(.appSurface)) |
| 410 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) | 410 | .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) |
| 411 | .padding(.vertical, 12) | 411 | .padding(.vertical, 12) |
| 412 | } | 412 | } |
DomainDig/IntegrationsView.swift +3 −3
| @@ -52,7 +52,7 @@ struct IntegrationsSettingsView: View { | |||
| 52 | if !target.isEnabled { | 52 | if !target.isEnabled { |
| 53 | Text("Disabled") | 53 | Text("Disabled") |
| 54 | .font(.caption2) | 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 | .foregroundStyle(.secondary) | 158 | .foregroundStyle(.secondary) |
| 159 | 159 | ||
| 160 | if let failureReason = record.failureReason { | 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 | Text(failureReason) | 162 | Text(failureReason) |
| 163 | .font(.caption) | 163 | .font(.caption) |
| 164 | .foregroundStyle(failureColor) | 164 | .foregroundStyle(failureColor) |
| @@ -324,7 +324,7 @@ private struct IntegrationEditorView: View { | |||
| 324 | Section { | 324 | Section { |
| 325 | Text(validationMessage) | 325 | Text(validationMessage) |
| 326 | .font(.caption) | 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 | .padding(.vertical, 4) | 42 | .padding(.vertical, 4) |
| 43 | } | 43 | } |
| 44 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 44 | .listRowBackground(Color(.appSurface)) |
| 45 | 45 | ||
| 46 | if viewModel.monitoringLogs.isEmpty { | 46 | if viewModel.monitoringLogs.isEmpty { |
| 47 | Section { | 47 | Section { |
| @@ -53,7 +53,7 @@ struct MonitoringView: View { | |||
| 53 | showsCardBackground: false | 53 | showsCardBackground: false |
| 54 | ) | 54 | ) |
| 55 | } | 55 | } |
| 56 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 56 | .listRowBackground(Color(.appSurface)) |
| 57 | } else { | 57 | } else { |
| 58 | Section("Recent Runs") { | 58 | Section("Recent Runs") { |
| 59 | ForEach(viewModel.monitoringLogs) { log in | 59 | ForEach(viewModel.monitoringLogs) { log in |
| @@ -78,10 +78,10 @@ struct MonitoringView: View { | |||
| 78 | HStack(spacing: 8) { | 78 | HStack(spacing: 8) { |
| 79 | metricBadge(title: "\(log.domainsChecked) checked") | 79 | metricBadge(title: "\(log.domainsChecked) checked") |
| 80 | if log.changesFound > 0 { | 80 | if log.changesFound > 0 { |
| 81 | metricBadge(title: "\(log.changesFound) changed", tint: .orange) | 81 | metricBadge(title: "\(log.changesFound) changed", tint: Color(.statusWarning)) |
| 82 | } | 82 | } |
| 83 | if log.alertsTriggered > 0 { | 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 | .scrollContentBackground(.hidden) | 95 | .scrollContentBackground(.hidden) |
| 96 | .background(Color.black) | 96 | .background(Color(.appBackground)) |
| 97 | .navigationTitle("Monitoring") | 97 | .navigationTitle("Monitoring") |
| 98 | .preferredColorScheme(.dark) | 98 | .preferredColorScheme(.dark) |
| 99 | .onAppear { | 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 | Text(title) | 105 | Text(title) |
| 106 | .font(appDensity.font(.caption2)) | 106 | .font(appDensity.font(.caption2)) |
| 107 | .foregroundStyle(tint) | 107 | .foregroundStyle(tint) |
| @@ -133,7 +133,7 @@ struct MonitoringLogDetailView: View { | |||
| 133 | LabeledContent("Alerts", value: "\(log.alertsTriggered)") | 133 | LabeledContent("Alerts", value: "\(log.alertsTriggered)") |
| 134 | LabeledContent("Timestamp", value: log.timestamp.formatted(date: .abbreviated, time: .shortened)) | 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 | Section("Domains") { | 138 | Section("Domains") { |
| 139 | ForEach(log.checkedDomains) { result in | 139 | ForEach(log.checkedDomains) { result in |
| @@ -167,7 +167,7 @@ struct MonitoringLogDetailView: View { | |||
| 167 | if let errorMessage = result.errorMessage { | 167 | if let errorMessage = result.errorMessage { |
| 168 | Text(errorMessage) | 168 | Text(errorMessage) |
| 169 | .font(appDensity.font(.caption2)) | 169 | .font(appDensity.font(.caption2)) |
| 170 | .foregroundStyle(.yellow) | 170 | .foregroundStyle(Color(.statusWarning)) |
| 171 | } | 171 | } |
| 172 | 172 | ||
| 173 | if let historyEntryID = result.historyEntryID, | 173 | if let historyEntryID = result.historyEntryID, |
| @@ -181,7 +181,7 @@ struct MonitoringLogDetailView: View { | |||
| 181 | .padding(.vertical, 4) | 181 | .padding(.vertical, 4) |
| 182 | } | 182 | } |
| 183 | } | 183 | } |
| 184 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 184 | .listRowBackground(Color(.appSurface)) |
| 185 | 185 | ||
| 186 | if !log.errors.isEmpty { | 186 | if !log.errors.isEmpty { |
| 187 | Section("Errors") { | 187 | Section("Errors") { |
| @@ -191,11 +191,11 @@ struct MonitoringLogDetailView: View { | |||
| 191 | .foregroundStyle(.secondary) | 191 | .foregroundStyle(.secondary) |
| 192 | } | 192 | } |
| 193 | } | 193 | } |
| 194 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 194 | .listRowBackground(Color(.appSurface)) |
| 195 | } | 195 | } |
| 196 | } | 196 | } |
| 197 | .scrollContentBackground(.hidden) | 197 | .scrollContentBackground(.hidden) |
| 198 | .background(Color.black) | 198 | .background(Color(.appBackground)) |
| 199 | .navigationTitle("Run Details") | 199 | .navigationTitle("Run Details") |
| 200 | .preferredColorScheme(.dark) | 200 | .preferredColorScheme(.dark) |
| 201 | } | 201 | } |
| @@ -205,9 +205,9 @@ struct MonitoringLogDetailView: View { | |||
| 205 | case .info: | 205 | case .info: |
| 206 | return .secondary | 206 | return .secondary |
| 207 | case .warning: | 207 | case .warning: |
| 208 | return .yellow | 208 | return Color(.statusWarning) |
| 209 | case .critical: | 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 | if let errorMessage = purchaseService.errorMessage { | 75 | if let errorMessage = purchaseService.errorMessage { |
| 76 | Text(errorMessage) | 76 | Text(errorMessage) |
| 77 | .font(appDensity.font(.caption, design: .default)) | 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 | Text("No saved domains") | 10 | Text("No saved domains") |
| 11 | .font(.system(.callout, design: .monospaced)) | 11 | .font(.system(.callout, design: .monospaced)) |
| 12 | .foregroundStyle(.secondary) | 12 | .foregroundStyle(.secondary) |
| 13 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 13 | .listRowBackground(Color(.appSurface)) |
| 14 | } else { | 14 | } else { |
| 15 | ForEach(viewModel.savedDomains, id: \.self) { domain in | 15 | ForEach(viewModel.savedDomains, id: \.self) { domain in |
| 16 | Button { | 16 | Button { |
| @@ -22,7 +22,7 @@ struct SavedDomainsView: View { | |||
| 22 | .font(.system(.callout, design: .monospaced)) | 22 | .font(.system(.callout, design: .monospaced)) |
| 23 | .foregroundStyle(.primary) | 23 | .foregroundStyle(.primary) |
| 24 | } | 24 | } |
| 25 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 25 | .listRowBackground(Color(.appSurface)) |
| 26 | } | 26 | } |
| 27 | .onDelete { offsets in | 27 | .onDelete { offsets in |
| 28 | viewModel.removeSavedDomains(at: offsets) | 28 | viewModel.removeSavedDomains(at: offsets) |
| @@ -30,7 +30,7 @@ struct SavedDomainsView: View { | |||
| 30 | } | 30 | } |
| 31 | } | 31 | } |
| 32 | .scrollContentBackground(.hidden) | 32 | .scrollContentBackground(.hidden) |
| 33 | .background(Color.black) | 33 | .background(Color(.appBackground)) |
| 34 | .navigationTitle("Saved Domains") | 34 | .navigationTitle("Saved Domains") |
| 35 | .toolbar { | 35 | .toolbar { |
| 36 | if !viewModel.savedDomains.isEmpty { | 36 | if !viewModel.savedDomains.isEmpty { |
DomainDig/ScheduledReportsView.swift +4 −4
| @@ -67,7 +67,7 @@ struct ScheduledReportsView: View { | |||
| 67 | } | 67 | } |
| 68 | .disabled(isGenerating || !FeatureAccessService.hasAccess(to: .automatedMonitoring)) | 68 | .disabled(isGenerating || !FeatureAccessService.hasAccess(to: .automatedMonitoring)) |
| 69 | } | 69 | } |
| 70 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 70 | .listRowBackground(Color(.appSurface)) |
| 71 | 71 | ||
| 72 | if logs.isEmpty { | 72 | if logs.isEmpty { |
| 73 | Section { | 73 | Section { |
| @@ -79,7 +79,7 @@ struct ScheduledReportsView: View { | |||
| 79 | showsCardBackground: false | 79 | showsCardBackground: false |
| 80 | ) | 80 | ) |
| 81 | } | 81 | } |
| 82 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 82 | .listRowBackground(Color(.appSurface)) |
| 83 | } else { | 83 | } else { |
| 84 | Section("Recent Reports") { | 84 | Section("Recent Reports") { |
| 85 | ForEach(logs) { log in | 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 | .scrollContentBackground(.hidden) | 102 | .scrollContentBackground(.hidden) |
| 103 | .background(Color.black) | 103 | .background(Color(.appBackground)) |
| 104 | .navigationTitle("Scheduled Reports") | 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 | .scrollContentBackground(.hidden) | 52 | .scrollContentBackground(.hidden) |
| 53 | .background(Color.black) | 53 | .background(Color(.appBackground)) |
| 54 | .navigationTitle(domain) | 54 | .navigationTitle(domain) |
| 55 | .toolbar { | 55 | .toolbar { |
| 56 | ToolbarItemGroup(placement: .topBarTrailing) { | 56 | ToolbarItemGroup(placement: .topBarTrailing) { |
| @@ -116,8 +116,8 @@ private struct TimelineRow: View { | |||
| 116 | model: .init( | 116 | model: .init( |
| 117 | title: severity.title, | 117 | title: severity.title, |
| 118 | systemImage: "arrow.triangle.2.circlepath", | 118 | systemImage: "arrow.triangle.2.circlepath", |
| 119 | foregroundColor: severity == .high ? .red : .yellow, | 119 | foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), |
| 120 | backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16) | 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 | .padding() | 204 | .padding() |
| 205 | } | 205 | } |
| 206 | .background(Color.black) | 206 | .background(Color(.appBackground)) |
| 207 | .navigationTitle("Compare Snapshots") | 207 | .navigationTitle("Compare Snapshots") |
| 208 | .navigationBarTitleDisplayMode(.inline) | 208 | .navigationBarTitleDisplayMode(.inline) |
| 209 | .onAppear { | 209 | .onAppear { |
DomainDig/WatchlistView.swift +23 −23
| @@ -38,7 +38,7 @@ struct WatchlistView: View { | |||
| 38 | Section("Refresh Progress") { | 38 | Section("Refresh Progress") { |
| 39 | VStack(alignment: .leading, spacing: 8) { | 39 | VStack(alignment: .leading, spacing: 8) { |
| 40 | ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) | 40 | ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) |
| 41 | .tint(.cyan) | 41 | .tint(Color(.statusInfo)) |
| 42 | HStack { | 42 | HStack { |
| 43 | Text(viewModel.batchProgressLabel) | 43 | Text(viewModel.batchProgressLabel) |
| 44 | .font(appDensity.font(.caption)) | 44 | .font(appDensity.font(.caption)) |
| @@ -59,7 +59,7 @@ struct WatchlistView: View { | |||
| 59 | } | 59 | } |
| 60 | .padding(.vertical, 4) | 60 | .padding(.vertical, 4) |
| 61 | } | 61 | } |
| 62 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 62 | .listRowBackground(Color(.appSurface)) |
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | if viewModel.filteredTrackedDomains.isEmpty { | 65 | if viewModel.filteredTrackedDomains.isEmpty { |
| @@ -72,7 +72,7 @@ struct WatchlistView: View { | |||
| 72 | showsCardBackground: false | 72 | showsCardBackground: false |
| 73 | ) | 73 | ) |
| 74 | } | 74 | } |
| 75 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 75 | .listRowBackground(Color(.appSurface)) |
| 76 | } else { | 76 | } else { |
| 77 | if let limitMessage = FeatureAccessService.trackedDomainLimitMessage(currentCount: viewModel.trackedDomains.count) { | 77 | if let limitMessage = FeatureAccessService.trackedDomainLimitMessage(currentCount: viewModel.trackedDomains.count) { |
| 78 | Section { | 78 | Section { |
| @@ -80,7 +80,7 @@ struct WatchlistView: View { | |||
| 80 | .font(appDensity.font(.caption)) | 80 | .font(appDensity.font(.caption)) |
| 81 | .foregroundStyle(.secondary) | 81 | .foregroundStyle(.secondary) |
| 82 | } | 82 | } |
| 83 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 83 | .listRowBackground(Color(.appSurface)) |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | if !pinnedDomains.isEmpty { | 86 | if !pinnedDomains.isEmpty { |
| @@ -94,7 +94,7 @@ struct WatchlistView: View { | |||
| 94 | } | 94 | } |
| 95 | .animation(.easeInOut(duration: 0.2), value: viewModel.filteredTrackedDomains.map(\.id)) | 95 | .animation(.easeInOut(duration: 0.2), value: viewModel.filteredTrackedDomains.map(\.id)) |
| 96 | .scrollContentBackground(.hidden) | 96 | .scrollContentBackground(.hidden) |
| 97 | .background(Color.black) | 97 | .background(Color(.appBackground)) |
| 98 | .navigationTitle("Watchlist") | 98 | .navigationTitle("Watchlist") |
| 99 | .searchable(text: $viewModel.watchlistSearchText, prompt: "Search tracked domains") | 99 | .searchable(text: $viewModel.watchlistSearchText, prompt: "Search tracked domains") |
| 100 | .toolbar { | 100 | .toolbar { |
| @@ -215,7 +215,7 @@ struct WatchlistView: View { | |||
| 215 | Section { | 215 | Section { |
| 216 | Text(addDomainError) | 216 | Text(addDomainError) |
| 217 | .font(appDensity.font(.caption)) | 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 | } label: { | 317 | } label: { |
| 318 | Label("Refresh", systemImage: "arrow.clockwise") | 318 | Label("Refresh", systemImage: "arrow.clockwise") |
| 319 | } | 319 | } |
| 320 | .tint(.cyan) | 320 | .tint(Color(.statusInfo)) |
| 321 | 321 | ||
| 322 | Button { | 322 | Button { |
| 323 | viewModel.togglePinned(for: trackedDomain) | 323 | viewModel.togglePinned(for: trackedDomain) |
| 324 | } label: { | 324 | } label: { |
| 325 | Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin") | 325 | Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin") |
| 326 | } | 326 | } |
| 327 | .tint(.yellow) | 327 | .tint(Color(.statusWarning)) |
| 328 | } | 328 | } |
| 329 | .swipeActions(edge: .trailing, allowsFullSwipe: false) { | 329 | .swipeActions(edge: .trailing, allowsFullSwipe: false) { |
| 330 | Button { | 330 | Button { |
| @@ -333,7 +333,7 @@ struct WatchlistView: View { | |||
| 333 | } label: { | 333 | } label: { |
| 334 | Label("Refresh", systemImage: "arrow.clockwise") | 334 | Label("Refresh", systemImage: "arrow.clockwise") |
| 335 | } | 335 | } |
| 336 | .tint(.cyan) | 336 | .tint(Color(.statusInfo)) |
| 337 | 337 | ||
| 338 | if viewModel.canDelete(trackedDomain) { | 338 | if viewModel.canDelete(trackedDomain) { |
| 339 | Button(role: .destructive) { | 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 | private var batchSummaryBinding: Binding<BatchSweepSummary?> { | 386 | private var batchSummaryBinding: Binding<BatchSweepSummary?> { |
| @@ -437,7 +437,7 @@ struct WatchlistRowView: View { | |||
| 437 | if trackedDomain.isPinned { | 437 | if trackedDomain.isPinned { |
| 438 | Image(systemName: "pin.fill") | 438 | Image(systemName: "pin.fill") |
| 439 | .font(.caption2) | 439 | .font(.caption2) |
| 440 | .foregroundStyle(.yellow) | 440 | .foregroundStyle(Color(.statusWarning)) |
| 441 | } | 441 | } |
| 442 | Text(trackedDomain.domain) | 442 | Text(trackedDomain.domain) |
| 443 | .font(appDensity.font(.callout)) | 443 | .font(appDensity.font(.callout)) |
| @@ -502,7 +502,7 @@ struct WatchlistRowView: View { | |||
| 502 | @ViewBuilder | 502 | @ViewBuilder |
| 503 | private var statusBadge: some View { | 503 | private var statusBadge: some View { |
| 504 | if isRefreshing { | 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 | } else { | 506 | } else { |
| 507 | AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability)) | 507 | AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability)) |
| 508 | } | 508 | } |
| @@ -516,8 +516,8 @@ struct WatchlistRowView: View { | |||
| 516 | model: .init( | 516 | model: .init( |
| 517 | title: "Shared", | 517 | title: "Shared", |
| 518 | systemImage: "person.2.fill", | 518 | systemImage: "person.2.fill", |
| 519 | foregroundColor: .cyan, | 519 | foregroundColor: Color(.statusInfo), |
| 520 | backgroundColor: .cyan.opacity(0.16) | 520 | backgroundColor: Color(.statusInfo).opacity(0.16) |
| 521 | ) | 521 | ) |
| 522 | ) | 522 | ) |
| 523 | } | 523 | } |
| @@ -534,11 +534,11 @@ struct WatchlistRowView: View { | |||
| 534 | let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon" | 534 | let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon" |
| 535 | switch trackedDomain.certificateWarningLevel { | 535 | switch trackedDomain.certificateWarningLevel { |
| 536 | case .critical: | 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 | case .warning: | 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 | case .none: | 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 | isRefreshing: viewModel.refreshingTrackedDomainID == liveTrackedDomain.id | 577 | isRefreshing: viewModel.refreshingTrackedDomainID == liveTrackedDomain.id |
| 578 | ) | 578 | ) |
| 579 | } | 579 | } |
| 580 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 580 | .listRowBackground(Color(.appSurface)) |
| 581 | 581 | ||
| 582 | Section { | 582 | Section { |
| 583 | Button { | 583 | Button { |
| @@ -652,13 +652,13 @@ struct TrackedDomainDetailView: View { | |||
| 652 | Label(liveTrackedDomain.collaboration?.isShared == true ? "Manage Share" : "Share Domain", systemImage: "person.2") | 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 | if !liveTrackedDomain.tags.isEmpty { | 657 | if !liveTrackedDomain.tags.isEmpty { |
| 658 | Section("Tags") { | 658 | Section("Tags") { |
| 659 | TagChipRowView(tags: liveTrackedDomain.tags) | 659 | TagChipRowView(tags: liveTrackedDomain.tags) |
| 660 | } | 660 | } |
| 661 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 661 | .listRowBackground(Color(.appSurface)) |
| 662 | } | 662 | } |
| 663 | 663 | ||
| 664 | Section("Monitoring Status") { | 664 | Section("Monitoring Status") { |
| @@ -672,7 +672,7 @@ struct TrackedDomainDetailView: View { | |||
| 672 | LabeledContent("Queued Alerts", value: "\(liveTrackedDomain.pendingMonitoringAlerts.count)") | 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 | if let summary = viewModel.latestChangeSummary(for: liveTrackedDomain) { | 677 | if let summary = viewModel.latestChangeSummary(for: liveTrackedDomain) { |
| 678 | Section("Latest Change Summary") { | 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 | .scrollContentBackground(.hidden) | 723 | .scrollContentBackground(.hidden) |
| 724 | .background(Color.black) | 724 | .background(Color(.appBackground)) |
| 725 | .navigationTitle(liveTrackedDomain.domain) | 725 | .navigationTitle(liveTrackedDomain.domain) |
| 726 | .preferredColorScheme(.dark) | 726 | .preferredColorScheme(.dark) |
| 727 | .onChange(of: viewModel.rerunNavigationToken) { _, _ in | 727 | .onChange(of: viewModel.rerunNavigationToken) { _, _ in |
DomainDig/WorkflowsView.swift +9 −9
| @@ -24,13 +24,13 @@ struct WorkflowsView: View { | |||
| 24 | .font(appDensity.font(.caption)) | 24 | .font(appDensity.font(.caption)) |
| 25 | .foregroundStyle(.secondary) | 25 | .foregroundStyle(.secondary) |
| 26 | } | 26 | } |
| 27 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 27 | .listRowBackground(Color(.appSurface)) |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | workflowContent | 30 | workflowContent |
| 31 | } | 31 | } |
| 32 | .scrollContentBackground(.hidden) | 32 | .scrollContentBackground(.hidden) |
| 33 | .background(Color.black) | 33 | .background(Color(.appBackground)) |
| 34 | .refreshable { | 34 | .refreshable { |
| 35 | await viewModel.refreshWorkflowList() | 35 | await viewModel.refreshWorkflowList() |
| 36 | } | 36 | } |
| @@ -73,7 +73,7 @@ struct WorkflowsView: View { | |||
| 73 | value: Double(viewModel.batchCompletedCount), | 73 | value: Double(viewModel.batchCompletedCount), |
| 74 | total: Double(max(viewModel.batchTotalCount, 1)) | 74 | total: Double(max(viewModel.batchTotalCount, 1)) |
| 75 | ) | 75 | ) |
| 76 | .tint(.cyan) | 76 | .tint(Color(.statusInfo)) |
| 77 | 77 | ||
| 78 | HStack { | 78 | HStack { |
| 79 | Text(viewModel.batchProgressLabel) | 79 | Text(viewModel.batchProgressLabel) |
| @@ -91,7 +91,7 @@ struct WorkflowsView: View { | |||
| 91 | } | 91 | } |
| 92 | .padding(.vertical, 4) | 92 | .padding(.vertical, 4) |
| 93 | } | 93 | } |
| 94 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 94 | .listRowBackground(Color(.appSurface)) |
| 95 | } | 95 | } |
| 96 | 96 | ||
| 97 | if viewModel.workflows.isEmpty { | 97 | if viewModel.workflows.isEmpty { |
| @@ -104,7 +104,7 @@ struct WorkflowsView: View { | |||
| 104 | showsCardBackground: false | 104 | showsCardBackground: false |
| 105 | ) | 105 | ) |
| 106 | } | 106 | } |
| 107 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 107 | .listRowBackground(Color(.appSurface)) |
| 108 | } else { | 108 | } else { |
| 109 | ForEach(viewModel.workflows) { workflow in | 109 | ForEach(viewModel.workflows) { workflow in |
| 110 | NavigationLink { | 110 | NavigationLink { |
| @@ -112,7 +112,7 @@ struct WorkflowsView: View { | |||
| 112 | } label: { | 112 | } label: { |
| 113 | WorkflowRowView(workflow: workflow) | 113 | WorkflowRowView(workflow: workflow) |
| 114 | } | 114 | } |
| 115 | .listRowBackground(Color(.systemGray6).opacity(0.5)) | 115 | .listRowBackground(Color(.appSurface)) |
| 116 | } | 116 | } |
| 117 | .onDelete { offsets in | 117 | .onDelete { offsets in |
| 118 | let workflows = offsets.map { viewModel.workflows[$0] } | 118 | let workflows = offsets.map { viewModel.workflows[$0] } |
| @@ -133,7 +133,7 @@ private struct WorkflowRowView: View { | |||
| 133 | if workflow.collaboration?.isShared == true { | 133 | if workflow.collaboration?.isShared == true { |
| 134 | Image(systemName: "person.2.fill") | 134 | Image(systemName: "person.2.fill") |
| 135 | .font(.caption2) | 135 | .font(.caption2) |
| 136 | .foregroundStyle(.cyan) | 136 | .foregroundStyle(Color(.statusInfo)) |
| 137 | } | 137 | } |
| 138 | Text(workflow.name) | 138 | Text(workflow.name) |
| 139 | .font(appDensity.font(.callout, design: .default, weight: .semibold)) | 139 | .font(appDensity.font(.callout, design: .default, weight: .semibold)) |
| @@ -335,7 +335,7 @@ struct WorkflowDetailView: View { | |||
| 335 | } | 335 | } |
| 336 | } | 336 | } |
| 337 | .scrollContentBackground(.hidden) | 337 | .scrollContentBackground(.hidden) |
| 338 | .background(Color.black) | 338 | .background(Color(.appBackground)) |
| 339 | .navigationTitle(workflow.name) | 339 | .navigationTitle(workflow.name) |
| 340 | .toolbar { | 340 | .toolbar { |
| 341 | if !workflow.domains.isEmpty { | 341 | if !workflow.domains.isEmpty { |
| @@ -640,7 +640,7 @@ struct WorkflowBulkAddSheet: View { | |||
| 640 | .foregroundStyle(.primary) | 640 | .foregroundStyle(.primary) |
| 641 | Spacer() | 641 | Spacer() |
| 642 | Image(systemName: selectedDomains.contains(domain) ? "checkmark.circle.fill" : "circle") | 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 | .buttonStyle(.plain) | 646 | .buttonStyle(.plain) |
DomainDigUITests/AccessibilityAuditHarness.swift +7 −1
| @@ -87,7 +87,13 @@ enum AccessibilityAuditHarness { | |||
| 87 | try app.performAccessibilityAudit { issue in | 87 | try app.performAccessibilityAudit { issue in |
| 88 | let isEnforced = !enforcedAuditTypes.intersection(issue.auditType).isEmpty | 88 | let isEnforced = !enforcedAuditTypes.intersection(issue.auditType).isEmpty |
| 89 | let marker = isEnforced ? "FAIL" : "report" | 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 | // true suppresses the finding, false reports it as a test failure. | 97 | // true suppresses the finding, false reports it as a test failure. |
| 92 | return !isEnforced | 98 | return !isEnforced |
| 93 | } | 99 | } |
DomainDigUITests/AccessibilityAuditTests.swift +14 −1
| @@ -17,7 +17,20 @@ final class AccessibilityAuditTests: XCTestCase { | |||
| 17 | // MARK: Per-screen audits | 17 | // MARK: Per-screen audits |
| 18 | 18 | ||
| 19 | func testInspectScreen() throws { | 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 | func testDashboardScreen() throws { | 36 | func testDashboardScreen() throws { |
DomainDigWidget/DomainDigPortfolioWidget.swift +10 −10
| @@ -91,9 +91,9 @@ struct DomainDigWidgetView: View { | |||
| 91 | Spacer(minLength: 0) | 91 | Spacer(minLength: 0) |
| 92 | 92 | ||
| 93 | HStack(spacing: 10) { | 93 | HStack(spacing: 10) { |
| 94 | countPill(data.healthyCount, .green) | 94 | countPill(data.healthyCount, Color(.statusPositive)) |
| 95 | countPill(data.warningCount, .orange) | 95 | countPill(data.warningCount, Color(.statusWarning)) |
| 96 | countPill(data.criticalCount, .red) | 96 | countPill(data.criticalCount, Color(.statusCritical)) |
| 97 | } | 97 | } |
| 98 | } | 98 | } |
| 99 | } | 99 | } |
| @@ -121,10 +121,10 @@ struct DomainDigWidgetView: View { | |||
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | HStack(spacing: 12) { | 123 | HStack(spacing: 12) { |
| 124 | summaryStat(data.healthyCount, "Healthy", .green) | 124 | summaryStat(data.healthyCount, "Healthy", Color(.statusPositive)) |
| 125 | summaryStat(data.warningCount, "Warning", .orange) | 125 | summaryStat(data.warningCount, "Warning", Color(.statusWarning)) |
| 126 | summaryStat(data.criticalCount, "Critical", .red) | 126 | summaryStat(data.criticalCount, "Critical", Color(.statusCritical)) |
| 127 | summaryStat(data.expiringSoonCount, "Expiring", .yellow) | 127 | summaryStat(data.expiringSoonCount, "Expiring", Color(.statusWarning)) |
| 128 | } | 128 | } |
| 129 | 129 | ||
| 130 | Divider() | 130 | Divider() |
| @@ -180,9 +180,9 @@ struct DomainDigWidgetView: View { | |||
| 180 | 180 | ||
| 181 | private func color(for status: DomainDigWidgetStatus) -> Color { | 181 | private func color(for status: DomainDigWidgetStatus) -> Color { |
| 182 | switch status { | 182 | switch status { |
| 183 | case .healthy: return .green | 183 | case .healthy: return Color(.statusPositive) |
| 184 | case .warning: return .orange | 184 | case .warning: return Color(.statusWarning) |
| 185 | case .critical: return .red | 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 | DynamicIslandExpandedRegion(.bottom) { | 24 | DynamicIslandExpandedRegion(.bottom) { |
| 25 | VStack(alignment: .leading, spacing: 4) { | 25 | VStack(alignment: .leading, spacing: 4) { |
| 26 | ProgressView(value: context.state.fractionComplete) | 26 | ProgressView(value: context.state.fractionComplete) |
| 27 | .tint(.cyan) | 27 | .tint(Color(.statusInfo)) |
| 28 | if let current = context.state.currentDomain { | 28 | if let current = context.state.currentDomain { |
| 29 | Text(current) | 29 | Text(current) |
| 30 | .font(.caption2) | 30 | .font(.caption2) |
| @@ -35,7 +35,7 @@ struct SweepLiveActivity: Widget { | |||
| 35 | } | 35 | } |
| 36 | } compactLeading: { | 36 | } compactLeading: { |
| 37 | Image(systemName: "arrow.trianglehead.2.clockwise") | 37 | Image(systemName: "arrow.trianglehead.2.clockwise") |
| 38 | .foregroundStyle(.cyan) | 38 | .foregroundStyle(Color(.statusInfo)) |
| 39 | } compactTrailing: { | 39 | } compactTrailing: { |
| 40 | Text("\(context.state.completed)/\(context.state.total)") | 40 | Text("\(context.state.completed)/\(context.state.total)") |
| 41 | .font(.caption2) | 41 | .font(.caption2) |
| @@ -43,7 +43,7 @@ struct SweepLiveActivity: Widget { | |||
| 43 | } minimal: { | 43 | } minimal: { |
| 44 | ProgressView(value: context.state.fractionComplete) | 44 | ProgressView(value: context.state.fractionComplete) |
| 45 | .progressViewStyle(.circular) | 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 | ProgressView(value: context.state.fractionComplete) | 68 | ProgressView(value: context.state.fractionComplete) |
| 69 | .tint(.cyan) | 69 | .tint(Color(.statusInfo)) |
| 70 | 70 | ||
| 71 | HStack { | 71 | HStack { |
| 72 | if let current = context.state.currentDomain { | 72 | if let current = context.state.currentDomain { |
| @@ -83,12 +83,12 @@ private struct SweepActivityBannerView: View { | |||
| 83 | if context.state.changed > 0 { | 83 | if context.state.changed > 0 { |
| 84 | Text("\(context.state.changed) changed") | 84 | Text("\(context.state.changed) changed") |
| 85 | .font(.caption2) | 85 | .font(.caption2) |
| 86 | .foregroundStyle(.orange) | 86 | .foregroundStyle(Color(.statusWarning)) |
| 87 | } | 87 | } |
| 88 | if context.state.warnings > 0 { | 88 | if context.state.warnings > 0 { |
| 89 | Text("\(context.state.warnings) warnings") | 89 | Text("\(context.state.warnings) warnings") |
| 90 | .font(.caption2) | 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 | } | ||