diff options
33 files changed, 1189 insertions, 253 deletions
diff --git a/Docs/ACCESSIBILITY.md b/Docs/ACCESSIBILITY.md index 1045df9..128bf3f 100644 --- a/Docs/ACCESSIBILITY.md +++ b/Docs/ACCESSIBILITY.md @@ -6,6 +6,58 @@ large Dynamic Type, element descriptions, trait correctness, and Dynamic Type support — the same ground the accessibility pass tracked in [issue #21](https://github.com/zerolabsco/domain-dig/issues/21) covers. +## The colour palette + +Semantic colours live in `Shared/Colors.xcassets`, which is inside the `Shared` +file-system-synchronized group and therefore reaches the app, the widget, and +the share extension automatically. `AccentColor` stays in +`DomainDig/Assets.xcassets` because it is the system-wide tint resolved via +`ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME`. + +Use the generated asset symbols — `Color(.statusCritical)`, `Color(.appSurface)` +— never a literal. `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS` +is on, so these are compile-time checked; a typo will not build. + +Every value clears WCAG AA (4.5:1) as text on its page, on its card, **and on +its own 16% badge tint** — the way `AppStatusBadgeView` actually draws it. The +worst of those three is shown: + +| Role | Light | Dark | Worst light | Worst dark | +| --- | --- | --- | --- | --- | +| `StatusInfo` / `AccentColor` | `#0000FF` | `#4DA3FF` | 5.50 | 5.85 | +| `StatusPositive` | `#146C2E` | `#30D158` | 4.65 | 7.32 | +| `StatusWarning` | `#7A5600` | `#FFD60A` | 4.74 | 9.61 | +| `StatusCritical` | `#B3261E` | `#FF6961` | 4.51 | 5.54 | +| `StatusNeutral` | `#5A5A5F` | `#A1A1A6` | 4.92 | 5.88 | + +High Contrast variants push further in the same direction. Surfaces +(`AppBackground`, `AppSurface`, `AppSurfaceElevated`, `AppSeparator`) carry no +meaning, so they get Any/Dark and, where useful, High Contrast — but no status +semantics. + +Why custom values instead of the system palette: **every** system colour fails +in light mode. Measured on white — systemYellow 1.51:1, systemOrange 2.20:1, +systemGreen 2.22:1, systemCyan 2.54:1, systemRed 3.55:1. All of them pass in +dark mode, which is why the dark-locked app looked fine and why unlocking light +mode is impossible without this work. + +### The accent has two roles, and they conflict + +An accent used as **text on a dark background** must be light. The same accent +used as a **fill behind a white label** must be dark. One value cannot do both: +`#4DA3FF` reads at 8.00:1 as text on black, but only 2.63:1 behind white text. + +So there are two colours: + +- `StatusInfo` / `AccentColor` — the accent as *foreground*: text, icons, + bordered-button labels, tab bar. +- `AccentFill` — the accent as a *filled background* behind a label, used by + `.borderedProminent`. Stays dark in both schemes so a white label clears AA + (8.59:1 light, 7.56:1 dark). + +`AppOnAccent` is the label colour for a solid accent fill and flips by scheme — +white on the light accent, black on the dark one. + ## Findings are reported, not enforced 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 ## Notes +- **Disabled controls are a false positive.** WCAG 1.4.3 exempts inactive + components from contrast requirements, but the audit flags them anyway. The + Inspect screen's Run button is disabled until a domain is typed, and auditing + the empty state reported a contrast failure that was never a real defect — + which is why `testInspectScreen` types a domain before auditing. Watch for + this before "fixing" a contrast finding on a disabled control. - Audits retry up to three times. Slower machines can miss the audit's internal deadline (`Audit failed to complete in time`, code `-56`), which is a tooling timeout, not an app defect. A screen that still cannot be audited is reported diff --git a/DomainDig/Assets.xcassets/AccentColor.colorset/Contents.json b/DomainDig/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..9d1e3b9 100644 --- a/DomainDig/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/DomainDig/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,11 +1,78 @@ { - "colors" : [ + "colors": [ { - "idiom" : "universal" + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0xFF", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x4D", + "green": "0xA3", + "blue": "0xFF", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0xCC", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x7F", + "green": "0xBF", + "blue": "0xFF", + "alpha": "1.000" + } + } } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 } } diff --git a/DomainDig/BatchResultsView.swift b/DomainDig/BatchResultsView.swift index 027d074..afef5cb 100644 --- a/DomainDig/BatchResultsView.swift +++ b/DomainDig/BatchResultsView.swift @@ -13,7 +13,7 @@ struct BatchResultsView: View { if viewModel.batchLookupRunning { VStack(alignment: .trailing, spacing: 4) { ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) - .tint(.cyan) + .tint(Color(.statusInfo)) .frame(width: 120) Text(viewModel.batchProgressLabel) .font(appDensity.font(.caption2)) @@ -110,7 +110,7 @@ struct BatchResultRowView: View { if let errorMessage = result.errorMessage { Text(errorMessage) .font(appDensity.font(.caption2)) - .foregroundStyle(result.status == .failed ? .red : .secondary) + .foregroundStyle(result.status == .failed ? Color(.statusCritical) : .secondary) } } .frame(maxWidth: .infinity, alignment: .leading) @@ -138,29 +138,29 @@ struct BatchResultRowView: View { title: availabilityText, systemImage: "exclamationmark.circle", foregroundColor: .secondary, - backgroundColor: Color(.systemGray5).opacity(0.55) + backgroundColor: Color(.appSurfaceElevated) ) } private var quickStatusBadge: AppStatusBadgeModel { switch result.status { case .pending: - return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) case .running: - return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) + return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) case .completed: if result.changeClassification == .critical || result.certificateWarningLevel == .critical || result.riskLevel == .high { - return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) } if result.changeClassification == .warning || result.changeSeverity == .medium || result.certificateWarningLevel == .warning { - return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) } if result.quickStatus == "Changed" { - return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) + return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) } - return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case .failed: - return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) } } } diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift index 40c97d1..7eec8a0 100644 --- a/DomainDig/ContentView.swift +++ b/DomainDig/ContentView.swift @@ -167,7 +167,7 @@ struct ContentView: View { } .background( LinearGradient( - colors: [Color.black, Color(.systemGray6).opacity(0.12)], + colors: [Color(.appBackground), Color(.appSurface)], startPoint: .top, endPoint: .bottom ) @@ -302,7 +302,7 @@ struct ContentView: View { .keyboardType(.URL) .padding(.horizontal, 12) .padding(.vertical, appDensity.metrics.controlVerticalPadding) - .background(Color(.systemGray6)) + .background(Color(.appSurfaceElevated)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) .focused($focusedInputField, equals: .singleDomain) .onSubmit { @@ -320,6 +320,7 @@ struct ContentView: View { .frame(minHeight: appDensity.metrics.controlMinHeight) } .buttonStyle(.borderedProminent) + .tint(Color(.accentFill)) .disabled(viewModel.trimmedDomain.isEmpty) } else { if FeatureAccessService.hasAccess(to: .batchOperations) { @@ -346,7 +347,7 @@ struct ContentView: View { .lineLimit(4...10) .padding(.horizontal, 12) .padding(.vertical, appDensity.metrics.controlVerticalPadding) - .background(Color(.systemGray6)) + .background(Color(.appSurfaceElevated)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) .focused($focusedInputField, equals: .bulkDomains) @@ -360,6 +361,7 @@ struct ContentView: View { .frame(minHeight: appDensity.metrics.controlMinHeight) } .buttonStyle(.borderedProminent) + .tint(Color(.accentFill)) .disabled(viewModel.bulkInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.batchLookupRunning) } } else { @@ -549,7 +551,7 @@ struct ContentView: View { } label: { Image(systemName: viewModel.isCurrentDomainSaved ? "bookmark.fill" : "bookmark") .font(appDensity.font(.body, design: .default)) - .foregroundStyle(viewModel.isCurrentDomainSaved ? .yellow : .secondary) + .foregroundStyle(viewModel.isCurrentDomainSaved ? Color(.statusWarning) : .secondary) } Menu { Button("Export TXT") { @@ -671,7 +673,7 @@ struct ContentView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, 8) .padding(.horizontal, 10) - .background(Color(.systemGray6).opacity(0.5)) + .background(Color(.appSurface)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) } } @@ -696,7 +698,7 @@ struct ContentView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(appDensity.metrics.cardPadding) - .background(Color(.systemGray6)) + .background(Color(.appSurfaceElevated)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) } @@ -792,7 +794,7 @@ struct SummaryView: View { .frame(minHeight: appDensity.metrics.rowMinHeight + 12, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) .padding(appDensity.metrics.cardPadding) - .background(Color(.systemGray6).opacity(0.5)) + .background(Color(.appSurface)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) } } @@ -851,22 +853,22 @@ struct RiskSummaryCardView: View { private var levelColor: Color { switch report.riskAssessment.level { case .low: - return .green + return Color(.statusPositive) case .medium: - return .yellow + return Color(.statusWarning) case .high: - return .red + return Color(.statusCritical) } } private func factorColor(_ impact: RiskImpact) -> Color { switch impact { case .positive: - return .green + return Color(.statusPositive) case .neutral: return .secondary case .negative: - return .red + return Color(.statusCritical) } } } @@ -888,7 +890,7 @@ struct InsightsSummaryCardView: View { HStack(alignment: .top, spacing: 8) { Image(systemName: "chart.line.uptrend.xyaxis") .font(appDensity.font(.caption2)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) .padding(.top, 2) Text(insight) .font(appDensity.font(.caption)) @@ -995,13 +997,13 @@ struct LookupStatusBannerView: View { private var color: Color { switch resultSource { case .live: - return .green + return Color(.statusPositive) case .cached: return .secondary case .mixed: - return .yellow + return Color(.statusWarning) case .snapshot: - return .orange + return Color(.statusWarning) } } @@ -1029,7 +1031,7 @@ struct DomainChangeSummaryView: View { HStack { Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle") .font(appDensity.font(.caption)) - .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .green) + .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : Color(.statusPositive)) Spacer() Text(summary.severity.title.uppercased()) .font(appDensity.font(.caption2)) @@ -1079,13 +1081,13 @@ struct DomainChangeSummaryView: View { if let riskScoreDelta = summary.riskScoreDelta { Text("Risk delta: \(riskScoreDelta >= 0 ? "+" : "")\(riskScoreDelta)") .font(appDensity.font(.caption2)) - .foregroundStyle(riskScoreDelta > 0 ? .orange : .secondary) + .foregroundStyle(riskScoreDelta > 0 ? Color(.statusWarning) : .secondary) } if let contextNote = summary.contextNote { Text(contextNote) .font(appDensity.font(.caption2)) - .foregroundStyle(.orange) + .foregroundStyle(Color(.statusWarning)) } } .padding(.top, 4) @@ -1101,9 +1103,9 @@ struct DomainChangeSummaryView: View { case .low: return .secondary case .medium: - return .yellow + return Color(.statusWarning) case .high: - return .red + return Color(.statusCritical) } } @@ -1205,7 +1207,7 @@ struct DomainDiffView: View { } } .padding(10) - .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.systemGray6).opacity(0.25)) + .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.appSurface)) .cornerRadius(8) } } label: { @@ -1225,7 +1227,7 @@ struct DomainDiffView: View { .overlay { if highlightedSectionID == section.id { RoundedRectangle(cornerRadius: 12) - .stroke(Color.cyan.opacity(0.55), lineWidth: 1) + .stroke(Color(.statusInfo).opacity(0.55), lineWidth: 1) } } } @@ -1258,9 +1260,9 @@ struct DomainDiffView: View { case .low: return .blue case .medium: - return .yellow + return Color(.statusWarning) case .high: - return .red + return Color(.statusCritical) } } @@ -1269,9 +1271,9 @@ struct DomainDiffView: View { case .low: return .blue case .medium: - return .yellow + return Color(.statusWarning) case .high: - return .red + return Color(.statusCritical) } } @@ -1337,7 +1339,7 @@ struct DomainSectionView: View { CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) { if let trackedDomain { HStack(spacing: 8) { - AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))) + AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))) Button { onTogglePinned() } label: { @@ -1638,7 +1640,7 @@ struct IntelligenceSectionView: View { if item.isEphemeral { Text("Ephemeral") .font(appDensity.font(.caption2)) - .foregroundStyle(.yellow) + .foregroundStyle(Color(.statusWarning)) } } 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 { VStack(alignment: .leading, spacing: 8) { Text(title) .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) content() } } @@ -1758,7 +1760,7 @@ struct SubdomainsSectionView: View { HStack { Text("\(group.label).*") .font(appDensity.font(.caption)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) Spacer() Text("\(group.subdomains.count)") .font(appDensity.font(.caption2)) @@ -1776,10 +1778,10 @@ struct SubdomainsSectionView: View { if row.isInteresting { Text("Interesting") .font(.system(.caption2, design: .monospaced)) - .foregroundStyle(.yellow) + .foregroundStyle(Color(.statusWarning)) .padding(.horizontal, 8) .padding(.vertical, 4) - .background(Color.yellow.opacity(0.14)) + .background(Color(.statusWarning).opacity(0.14)) .clipShape(Capsule()) } } @@ -1883,7 +1885,7 @@ struct DNSSectionView: View { Text(section.title) .font(.system(.subheadline, design: .monospaced)) .fontWeight(.semibold) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) if let message = section.message { MessageRowView(text: message.text, isError: message.isError) @@ -1910,7 +1912,7 @@ struct DNSSectionView: View { Text("PTR") .font(.system(.subheadline, design: .monospaced)) .fontWeight(.semibold) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) SectionTrustMetadataView(provenance: ptrProvenance, confidence: nil) MessageRowView(text: ptrMessage.text, isError: ptrMessage.isError) } @@ -1920,7 +1922,7 @@ struct DNSSectionView: View { HStack { Text("History") .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) Spacer() if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder { Button("Load") { @@ -1990,7 +1992,7 @@ struct WebSectionView: View { HStack { Text("TLS") .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) Spacer() if !sslLoading { AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: sslInfo, error: sslError)) @@ -2034,7 +2036,7 @@ struct WebSectionView: View { CardView { Text("Headers") .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) SectionTrustMetadataView(provenance: httpProvenance, confidence: nil) if headersLoading { ProgressView("Fetching headers…") @@ -2052,7 +2054,7 @@ struct WebSectionView: View { HStack(alignment: .top, spacing: 4) { Text(header.name + ":") .font(appDensity.font(.caption)) - .foregroundStyle(header.isSecurityHeader ? .yellow : .cyan) + .foregroundStyle(header.isSecurityHeader ? Color(.statusWarning) : Color(.statusInfo)) Text(header.value) .font(appDensity.font(.caption)) .foregroundStyle(.primary) @@ -2067,7 +2069,7 @@ struct WebSectionView: View { HStack { Text("Redirects") .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) Spacer() if let finalURL { AppCopyButton(value: finalURL, label: "Copy redirect URL") @@ -2093,7 +2095,7 @@ struct WebSectionView: View { .frame(width: 16, alignment: .trailing) Text(redirect.statusCode) .font(appDensity.font(.caption)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) .frame(width: 36, alignment: .leading) Text(redirect.url) .font(appDensity.font(.caption)) @@ -2163,7 +2165,7 @@ struct EmailSectionView: View { HStack(spacing: 8) { Text(row.label) .font(appDensity.font(.caption)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) .frame(width: 76, alignment: .leading) AppStatusBadgeView(model: emailRowBadge(row)) } @@ -2186,13 +2188,13 @@ struct EmailSectionView: View { private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel { switch row.statusTone { case .success: - return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case .warning: - return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .failure: - return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) case .primary, .secondary: - return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } } } @@ -2227,7 +2229,7 @@ struct NetworkSectionView: View { CardView { Text("Reachability") .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) SectionTrustMetadataView(provenance: reachabilityProvenance, confidence: nil) if reachabilityLoading { ProgressView("Checking ports…") @@ -2252,7 +2254,7 @@ struct NetworkSectionView: View { CardView(allowsHorizontalScroll: false) { Text("Location") .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) SectionTrustMetadataView(provenance: geolocationProvenance, confidence: geolocationConfidence) if geolocationLoading { ProgressView("Looking up location…") @@ -2284,13 +2286,13 @@ struct NetworkSectionView: View { CardView(allowsHorizontalScroll: false) { Text("Port Scan") .font(appDensity.font(.subheadline, weight: .semibold)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) SectionTrustMetadataView(provenance: portScanProvenance, confidence: nil) if isCloudflareProxied { Text("Domain is behind Cloudflare's proxy. Results reflect the edge, not the origin.") .font(appDensity.font(.caption2)) - .foregroundStyle(.orange) + .foregroundStyle(Color(.statusWarning)) .fixedSize(horizontal: false, vertical: true) } @@ -2314,7 +2316,7 @@ struct NetworkSectionView: View { .autocorrectionDisabled() .keyboardType(.numberPad) .padding(10) - .background(Color(.systemGray6).opacity(0.5)) + .background(Color(.appSurface)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) Button("Scan") { @@ -2322,6 +2324,7 @@ struct NetworkSectionView: View { onScanCustomPorts() } .buttonStyle(.borderedProminent) + .tint(Color(.accentFill)) .disabled(customPortScanLoading) if customPortScanLoading { @@ -2344,13 +2347,13 @@ struct NetworkSectionView: View { private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel { switch row.statusTone { case .success: - return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case .warning: - return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .failure: - return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) case .primary, .secondary: - return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } } } @@ -2395,13 +2398,13 @@ struct PortRowsView: View { private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel { switch row.statusTone { case .success: - return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case .warning: - return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .failure: - return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) case .primary, .secondary: - return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } } } @@ -2413,7 +2416,7 @@ struct SectionTitleView: View { var body: some View { Text(title) .font(appDensity.font(.headline, design: .default, weight: .semibold)) - .foregroundStyle(.white) + .foregroundStyle(.primary) } } @@ -2442,7 +2445,7 @@ struct CardView<Content: View>: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(appDensity.metrics.cardPadding) - .background(Color(.systemGray6).opacity(0.5)) + .background(Color(.appSurface)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) } @@ -2484,7 +2487,7 @@ struct MessageRowView: View { var body: some View { Label(text, systemImage: isError ? "exclamationmark.triangle.fill" : "info.circle") .font(appDensity.font(.caption)) - .foregroundStyle(isError ? .red : .secondary) + .foregroundStyle(isError ? Color(.statusCritical) : .secondary) .lineLimit(nil) .fixedSize(horizontal: false, vertical: true) } @@ -2576,6 +2579,11 @@ struct LabeledValueRow: View { } } +/// Maps a row's semantic tone onto the app palette. +/// +/// See `Docs/ACCESSIBILITY.md` for the measured contrast ratios behind these +/// colours. Never reach for a literal (`Color(.statusCritical)`, `Color(.statusWarning)`, …) — the system +/// palette fails WCAG AA badly in light mode (systemYellow is 1.28:1 on white). enum ResultColors { static func color(for tone: ResultTone) -> Color { switch tone { @@ -2584,11 +2592,11 @@ enum ResultColors { case .secondary: return .secondary case .success: - return .green + return Color(.statusPositive) case .warning: - return .yellow + return Color(.statusWarning) case .failure: - return .red + return Color(.statusCritical) } } } @@ -2654,7 +2662,7 @@ struct SettingsView: View { if let errorMessage = purchaseService.errorMessage { Text(errorMessage) .font(appDensity.font(.caption, design: .default)) - .foregroundStyle(.red) + .foregroundStyle(Color(.statusCritical)) } } @@ -2789,7 +2797,7 @@ private struct HistoryNetworkSettingsView: View { if let customResolverError { Text(customResolverError) .font(appDensity.font(.caption, design: .default)) - .foregroundStyle(.red) + .foregroundStyle(Color(.statusCritical)) } } } @@ -2871,7 +2879,7 @@ private struct CloudSyncSettingsView: View { if let lastErrorMessage = cloudSyncService.lastErrorMessage { Text(lastErrorMessage) .font(appDensity.font(.caption, design: .default)) - .foregroundStyle(.red) + .foregroundStyle(Color(.statusCritical)) } } } @@ -2964,7 +2972,7 @@ private struct LocalAPISettingsView: View { Spacer() Text("\(log.statusCode)") .font(appDensity.font(.caption, design: .default)) - .foregroundStyle(log.statusCode >= 400 ? .red : .secondary) + .foregroundStyle(log.statusCode >= 400 ? Color(.statusCritical) : .secondary) } Text(log.timestamp.formatted(date: .abbreviated, time: .standard)) @@ -3744,8 +3752,8 @@ extension ChangeImpactClassification { var color: Color { switch self { case .informational: return .secondary - case .warning: return .yellow - case .critical: return .red + case .warning: return Color(.statusWarning) + case .critical: return Color(.statusCritical) } } } diff --git a/DomainDig/DashboardView.swift b/DomainDig/DashboardView.swift index 398ff3e..da8d90a 100644 --- a/DomainDig/DashboardView.swift +++ b/DomainDig/DashboardView.swift @@ -22,20 +22,20 @@ struct DashboardView: View { showsCardBackground: false ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { Section { LazyVGrid(columns: summaryColumns, spacing: 10) { - summaryCard(title: "Total Domains", value: viewModel.portfolioDashboardData.snapshot.totalDomains, filter: .all, tint: .cyan) - summaryCard(title: "Healthy", value: viewModel.portfolioDashboardData.snapshot.healthyCount, filter: .healthy, tint: .green) - summaryCard(title: "Warning", value: viewModel.portfolioDashboardData.snapshot.warningCount, filter: .warning, tint: .yellow) - summaryCard(title: "Critical", value: viewModel.portfolioDashboardData.snapshot.criticalCount, filter: .critical, tint: .red) - summaryCard(title: "Changes (24h)", value: viewModel.portfolioDashboardData.snapshot.changedLast24h, filter: .changed, tint: .orange) - summaryCard(title: "Unreachable", value: viewModel.portfolioDashboardData.snapshot.unreachableCount, filter: .unreachable, tint: .pink) + summaryCard(title: "Total Domains", value: viewModel.portfolioDashboardData.snapshot.totalDomains, filter: .all, tint: Color(.statusInfo)) + summaryCard(title: "Healthy", value: viewModel.portfolioDashboardData.snapshot.healthyCount, filter: .healthy, tint: Color(.statusPositive)) + summaryCard(title: "Warning", value: viewModel.portfolioDashboardData.snapshot.warningCount, filter: .warning, tint: Color(.statusWarning)) + summaryCard(title: "Critical", value: viewModel.portfolioDashboardData.snapshot.criticalCount, filter: .critical, tint: Color(.statusCritical)) + summaryCard(title: "Changes (24h)", value: viewModel.portfolioDashboardData.snapshot.changedLast24h, filter: .changed, tint: Color(.statusWarning)) + summaryCard(title: "Unreachable", value: viewModel.portfolioDashboardData.snapshot.unreachableCount, filter: .unreachable, tint: Color(.statusCritical)) } .padding(.vertical, 4) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Quick Filters") { ScrollView(.horizontal, showsIndicators: false) { @@ -46,10 +46,10 @@ struct DashboardView: View { } label: { Text(filter.title) .font(appDensity.font(.caption, weight: .semibold)) - .foregroundStyle(viewModel.dashboardFilter == filter ? Color.black : Color.white) + .foregroundStyle(viewModel.dashboardFilter == filter ? Color(.appOnAccent) : Color.primary) .padding(.horizontal, 12) .padding(.vertical, 8) - .background(viewModel.dashboardFilter == filter ? Color.cyan : Color(.systemGray5).opacity(0.6)) + .background(viewModel.dashboardFilter == filter ? Color(.statusInfo) : Color(.appSurfaceElevated)) .clipShape(Capsule()) } .buttonStyle(.plain) @@ -58,7 +58,7 @@ struct DashboardView: View { .padding(.vertical, 4) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Recent Activity") { if viewModel.filteredPortfolioRecentActivity.isEmpty { @@ -76,7 +76,7 @@ struct DashboardView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Attention Required") { if viewModel.filteredPortfolioAttentionRequired.isEmpty { @@ -94,7 +94,7 @@ struct DashboardView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Expiring Soon") { if viewModel.filteredPortfolioExpiringSoon.isEmpty { @@ -110,7 +110,7 @@ struct DashboardView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Portfolio List") { if viewModel.filteredPortfolioGroups.isEmpty { @@ -150,11 +150,11 @@ struct DashboardView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Dashboard") .searchable(text: $viewModel.dashboardSearchText, prompt: "Search portfolio") .toolbar { @@ -196,7 +196,7 @@ struct DashboardView: View { .foregroundStyle(.secondary) Text("\(value)") .font(.system(size: 28, weight: .bold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.primary) HStack { Circle() .fill(tint) @@ -218,13 +218,13 @@ struct DashboardView: View { if viewModel.dashboardFilter == filter { return AnyShapeStyle( LinearGradient( - colors: [Color.cyan.opacity(0.28), Color.cyan.opacity(0.12)], + colors: [Color(.statusInfo).opacity(0.28), Color(.statusInfo).opacity(0.12)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) } - return AnyShapeStyle(Color(.systemGray5).opacity(0.45)) + return AnyShapeStyle(Color(.appSurfaceElevated)) } private func dashboardEmptyRow(_ message: String) -> some View { @@ -243,15 +243,15 @@ struct DashboardView: View { if criticalCount > 0 { title = "\(criticalCount) critical" - color = .red + color = Color(.statusCritical) systemImage = "exclamationmark.octagon.fill" } else if warningCount > 0 { title = "\(warningCount) warning" - color = .yellow + color = Color(.statusWarning) systemImage = "exclamationmark.triangle.fill" } else { title = "Healthy" - color = .green + color = Color(.statusPositive) systemImage = "checkmark.circle.fill" } @@ -297,11 +297,11 @@ private struct PortfolioActivityRow: View { private var iconColor: Color { switch item.health { case .healthy: - return .cyan + return Color(.statusInfo) case .warning: - return .yellow + return Color(.statusWarning) case .critical: - return .red + return Color(.statusCritical) } } } @@ -332,11 +332,11 @@ private struct PortfolioAttentionRow: View { private var badgeModel: AppStatusBadgeModel { switch item.health { case .healthy: - return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: "Healthy", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case .warning: - return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .critical: - return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: "Critical", systemImage: "exclamationmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) } } } @@ -371,11 +371,11 @@ private struct PortfolioExpiryRow: View { private var badgeModel: AppStatusBadgeModel { switch state.certificateExpiryState { case .none: - return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: "Healthy", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case .warning: - return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .critical: - return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: "Critical", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) } } } diff --git a/DomainDig/DomainCompareView.swift b/DomainDig/DomainCompareView.swift index 96341c2..90ee5b1 100644 --- a/DomainDig/DomainCompareView.swift +++ b/DomainDig/DomainCompareView.swift @@ -63,11 +63,11 @@ struct DomainCompareView: View { showsCardBackground: false ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Compare Domains") } diff --git a/DomainDig/DomainDig/AuditViews.swift b/DomainDig/DomainDig/AuditViews.swift index 8602bb3..df5a9a9 100644 --- a/DomainDig/DomainDig/AuditViews.swift +++ b/DomainDig/DomainDig/AuditViews.swift @@ -27,7 +27,7 @@ struct AuditListView: View { showsCardBackground: false ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { Section("Audit Domains") { ForEach(groupedSessions, id: \.domain) { group in @@ -65,11 +65,11 @@ struct AuditListView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Audit Mode") .toolbar { if !viewModel.searchedDomain.isEmpty { @@ -132,7 +132,7 @@ struct AuditDomainTimelineView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Trend") { ForEach(viewModel.auditTimeline(for: domain)) { point in @@ -152,10 +152,10 @@ struct AuditDomainTimelineView: View { .padding(.vertical, 2) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle(domain) .toolbar { ToolbarItem(placement: .topBarTrailing) { @@ -218,7 +218,7 @@ struct AuditSessionDetailView: View { .foregroundStyle(.secondary) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Audit Summary") { LabeledContent("Findings", value: "\(session.findings.count)") @@ -228,7 +228,7 @@ struct AuditSessionDetailView: View { LabeledContent("Highest Severity", value: severity.title) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Reviewer Notes") { TextEditor(text: $notesDraft) @@ -240,7 +240,7 @@ struct AuditSessionDetailView: View { viewModel.updateAuditNotes(notesDraft, sessionID: session.id) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Checklist") { ForEach(session.checklist) { item in @@ -249,7 +249,7 @@ struct AuditSessionDetailView: View { } label: { HStack(alignment: .top, spacing: 10) { Image(systemName: item.isComplete ? "checkmark.circle.fill" : "circle") - .foregroundStyle(item.isComplete ? Color.green : .secondary) + .foregroundStyle(item.isComplete ? Color(.statusPositive) : .secondary) VStack(alignment: .leading, spacing: 4) { Text(item.title) .font(appDensity.font(.callout)) @@ -264,7 +264,7 @@ struct AuditSessionDetailView: View { .buttonStyle(.plain) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Findings") { if session.findings.isEmpty { @@ -301,7 +301,7 @@ struct AuditSessionDetailView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Evidence Snapshot") { LabeledContent("Availability", value: availabilityTitle(session.evidence.report.availability)) @@ -311,7 +311,7 @@ struct AuditSessionDetailView: View { LabeledContent("Reachability", value: session.evidence.report.network.reachabilitySummary) LabeledContent("Registrar", value: session.evidence.report.ownership?.registrar ?? "Unavailable") } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) if !session.evidence.historicalContext.isEmpty { Section("Historical Context") { @@ -325,7 +325,7 @@ struct AuditSessionDetailView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } Section("Audit Timeline") { @@ -345,10 +345,10 @@ struct AuditSessionDetailView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Audit Session") .toolbar { ToolbarItemGroup(placement: .topBarTrailing) { @@ -398,7 +398,7 @@ struct AuditSessionDetailView: View { } } else { ContentUnavailableView("Audit Session Missing", systemImage: "exclamationmark.triangle") - .background(Color.black) + .background(Color(.appBackground)) } } .preferredColorScheme(.dark) @@ -477,7 +477,7 @@ private struct AuditFindingEditorView: View { Text(area.title) Spacer() Image(systemName: selectedAreas.contains(area) ? "checkmark.circle.fill" : "circle") - .foregroundStyle(selectedAreas.contains(area) ? Color.green : .secondary) + .foregroundStyle(selectedAreas.contains(area) ? Color(.statusPositive) : .secondary) } } .buttonStyle(.plain) @@ -528,11 +528,11 @@ private func auditStatusBadge(_ status: AuditStatus) -> some View { let model: AppStatusBadgeModel switch status { case .draft: - model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + model = .init(title: "Draft", systemImage: "square.and.pencil", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .inReview: - model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) + model = .init(title: "In Review", systemImage: "doc.text.magnifyingglass", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) case .complete: - model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + model = .init(title: "Complete", systemImage: "checkmark.seal.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) } return AppStatusBadgeView(model: model) } @@ -543,11 +543,11 @@ private func findingSeverityBadge(_ severity: AuditFindingSeverity) -> some View case .informational: color = .secondary case .low: - color = .green + color = Color(.statusPositive) case .medium: - color = .yellow + color = Color(.statusWarning) case .high: - color = .red + color = Color(.statusCritical) } return AppStatusBadgeView( model: .init( diff --git a/DomainDig/DomainDigUI.swift b/DomainDig/DomainDigUI.swift index 272c678..9c63e73 100644 --- a/DomainDig/DomainDigUI.swift +++ b/DomainDig/DomainDigUI.swift @@ -92,48 +92,48 @@ enum AppStatusFactory { static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel { switch status { case .available: - return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case .registered: - return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .unknown, .none: - return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } } static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel { if error != nil || sslInfo == nil { - return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) } if let sslInfo, sslInfo.daysUntilExpiry <= 14 { - return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) } - return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) } static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel { guard error == nil, let result else { - return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count switch foundCount { case 3: - return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) case 1, 2: - return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) default: - return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } } static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel { guard let summary else { - return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } if summary.hasChanges { - return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16)) + return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) } - return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55)) + return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)) } } @@ -184,9 +184,9 @@ struct AppCopyButton: View { } label: { Image(systemName: didCopy ? "checkmark" : "doc.on.doc") .font(appDensity.font(.caption)) - .foregroundStyle(didCopy ? Color.green : .secondary) + .foregroundStyle(didCopy ? Color(.statusPositive) : .secondary) .frame(width: 30, height: 30) - .background(Color(.systemGray5).opacity(0.35)) + .background(Color(.appSurfaceElevated)) .clipShape(RoundedRectangle(cornerRadius: 8)) } .buttonStyle(.plain) @@ -264,11 +264,11 @@ struct EmptyStateCardView: View { Text(suggestion) .font(appDensity.font(.caption)) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) } .frame(maxWidth: .infinity, alignment: .leading) .padding(appDensity.metrics.cardPadding) - .background(showsCardBackground ? Color(.systemGray6).opacity(0.45) : Color.clear) + .background(showsCardBackground ? Color(.appSurface) : Color.clear) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) } } @@ -307,7 +307,7 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View { VStack(alignment: .leading, spacing: 3) { Text(title) .font(appDensity.font(.headline, design: .default, weight: .semibold)) - .foregroundStyle(.white) + .foregroundStyle(.primary) if let subtitle { Text(subtitle) .font(appDensity.font(.caption)) @@ -346,7 +346,7 @@ struct TagChipRowView: View { .font(.caption) .padding(.horizontal, 10) .padding(.vertical, 5) - .background(Color(.systemGray5).opacity(0.6), in: Capsule()) + .background(Color(.appSurfaceElevated), in: Capsule()) } } } @@ -380,8 +380,8 @@ struct TagFilterChipRowView: View { .font(.caption) .padding(.horizontal, 10) .padding(.vertical, 5) - .background(isSelected ? Color.cyan.opacity(0.3) : Color(.systemGray5).opacity(0.6), in: Capsule()) - .foregroundStyle(isSelected ? Color.cyan : Color.primary) + .background(isSelected ? Color(.statusInfo).opacity(0.3) : Color(.appSurfaceElevated), in: Capsule()) + .foregroundStyle(isSelected ? Color(.statusInfo) : Color.primary) } .buttonStyle(.plain) } diff --git a/DomainDig/HistoryView.swift b/DomainDig/HistoryView.swift index 5350168..ffa7745 100644 --- a/DomainDig/HistoryView.swift +++ b/DomainDig/HistoryView.swift @@ -25,7 +25,7 @@ struct HistoryView: View { systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90", showsCardBackground: false ) - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { Section("Domains") { ForEach(domainSummaries, id: \.domain) { item in @@ -55,21 +55,21 @@ struct HistoryView: View { model: .init( title: severity.title, systemImage: "arrow.triangle.2.circlepath", - foregroundColor: severity == .high ? .red : .yellow, - backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16) + foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), + backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16) ) ) } } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("History") .searchable(text: $viewModel.timelineDomainFilter, prompt: "Search domains") .toolbar { @@ -321,7 +321,7 @@ struct HistoryDetailView: View { .padding(.horizontal) .padding(.bottom, 32) } - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle(entry.domain) .toolbar { ToolbarItemGroup(placement: .topBarTrailing) { @@ -390,12 +390,12 @@ struct HistoryDetailView: View { if let mismatchNote = viewModel.resolverMismatchNote(for: entry) { Text(mismatchNote) .font(appDensity.font(.caption2)) - .foregroundStyle(.orange) + .foregroundStyle(Color(.statusWarning)) } if entry.isPartialSnapshot { Text("Partial snapshot: \(entry.validationIssues.joined(separator: " | "))") .font(appDensity.font(.caption2)) - .foregroundStyle(.yellow) + .foregroundStyle(Color(.statusWarning)) } if let note = entry.note, !note.isEmpty { Text(note) @@ -406,7 +406,7 @@ struct HistoryDetailView: View { .foregroundStyle(.secondary) .padding(8) .frame(maxWidth: .infinity, alignment: .leading) - .background(Color(.systemGray6).opacity(0.3)) + .background(Color(.appSurface)) .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius)) .padding(.vertical, 12) } diff --git a/DomainDig/IntegrationsView.swift b/DomainDig/IntegrationsView.swift index 73f511a..163d4d1 100644 --- a/DomainDig/IntegrationsView.swift +++ b/DomainDig/IntegrationsView.swift @@ -52,7 +52,7 @@ struct IntegrationsSettingsView: View { if !target.isEnabled { Text("Disabled") .font(.caption2) - .foregroundStyle(.orange) + .foregroundStyle(Color(.statusWarning)) } } } @@ -158,7 +158,7 @@ private struct IntegrationDetailView: View { .foregroundStyle(.secondary) if let failureReason = record.failureReason { - let failureColor: Color = record.status == .skipped ? .secondary : .red + let failureColor: Color = record.status == .skipped ? .secondary : Color(.statusCritical) Text(failureReason) .font(.caption) .foregroundStyle(failureColor) @@ -324,7 +324,7 @@ private struct IntegrationEditorView: View { Section { Text(validationMessage) .font(.caption) - .foregroundStyle(.red) + .foregroundStyle(Color(.statusCritical)) } } } diff --git a/DomainDig/MonitoringView.swift b/DomainDig/MonitoringView.swift index fc0b8ab..decad35 100644 --- a/DomainDig/MonitoringView.swift +++ b/DomainDig/MonitoringView.swift @@ -41,7 +41,7 @@ struct MonitoringView: View { } .padding(.vertical, 4) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) if viewModel.monitoringLogs.isEmpty { Section { @@ -53,7 +53,7 @@ struct MonitoringView: View { showsCardBackground: false ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { Section("Recent Runs") { ForEach(viewModel.monitoringLogs) { log in @@ -78,10 +78,10 @@ struct MonitoringView: View { HStack(spacing: 8) { metricBadge(title: "\(log.domainsChecked) checked") if log.changesFound > 0 { - metricBadge(title: "\(log.changesFound) changed", tint: .orange) + metricBadge(title: "\(log.changesFound) changed", tint: Color(.statusWarning)) } if log.alertsTriggered > 0 { - metricBadge(title: "\(log.alertsTriggered) alerts", tint: .red) + metricBadge(title: "\(log.alertsTriggered) alerts", tint: Color(.statusCritical)) } } } @@ -89,11 +89,11 @@ struct MonitoringView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Monitoring") .preferredColorScheme(.dark) .onAppear { @@ -101,7 +101,7 @@ struct MonitoringView: View { } } - private func metricBadge(title: String, tint: Color = .cyan) -> some View { + private func metricBadge(title: String, tint: Color = Color(.statusInfo)) -> some View { Text(title) .font(appDensity.font(.caption2)) .foregroundStyle(tint) @@ -133,7 +133,7 @@ struct MonitoringLogDetailView: View { LabeledContent("Alerts", value: "\(log.alertsTriggered)") LabeledContent("Timestamp", value: log.timestamp.formatted(date: .abbreviated, time: .shortened)) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section("Domains") { ForEach(log.checkedDomains) { result in @@ -167,7 +167,7 @@ struct MonitoringLogDetailView: View { if let errorMessage = result.errorMessage { Text(errorMessage) .font(appDensity.font(.caption2)) - .foregroundStyle(.yellow) + .foregroundStyle(Color(.statusWarning)) } if let historyEntryID = result.historyEntryID, @@ -181,7 +181,7 @@ struct MonitoringLogDetailView: View { .padding(.vertical, 4) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) if !log.errors.isEmpty { Section("Errors") { @@ -191,11 +191,11 @@ struct MonitoringLogDetailView: View { .foregroundStyle(.secondary) } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Run Details") .preferredColorScheme(.dark) } @@ -205,9 +205,9 @@ struct MonitoringLogDetailView: View { case .info: return .secondary case .warning: - return .yellow + return Color(.statusWarning) case .critical: - return .red + return Color(.statusCritical) } } } diff --git a/DomainDig/PaywallView.swift b/DomainDig/PaywallView.swift index cad8be4..27e80ee 100644 --- a/DomainDig/PaywallView.swift +++ b/DomainDig/PaywallView.swift @@ -75,7 +75,7 @@ struct PaywallView: View { if let errorMessage = purchaseService.errorMessage { Text(errorMessage) .font(appDensity.font(.caption, design: .default)) - .foregroundStyle(.red) + .foregroundStyle(Color(.statusCritical)) } } diff --git a/DomainDig/SavedDomainsView.swift b/DomainDig/SavedDomainsView.swift index f407d78..59dcb73 100644 --- a/DomainDig/SavedDomainsView.swift +++ b/DomainDig/SavedDomainsView.swift @@ -10,7 +10,7 @@ struct SavedDomainsView: View { Text("No saved domains") .font(.system(.callout, design: .monospaced)) .foregroundStyle(.secondary) - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { ForEach(viewModel.savedDomains, id: \.self) { domain in Button { @@ -22,7 +22,7 @@ struct SavedDomainsView: View { .font(.system(.callout, design: .monospaced)) .foregroundStyle(.primary) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } .onDelete { offsets in viewModel.removeSavedDomains(at: offsets) @@ -30,7 +30,7 @@ struct SavedDomainsView: View { } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Saved Domains") .toolbar { if !viewModel.savedDomains.isEmpty { diff --git a/DomainDig/ScheduledReportsView.swift b/DomainDig/ScheduledReportsView.swift index 170e02d..dffafe3 100644 --- a/DomainDig/ScheduledReportsView.swift +++ b/DomainDig/ScheduledReportsView.swift @@ -67,7 +67,7 @@ struct ScheduledReportsView: View { } .disabled(isGenerating || !FeatureAccessService.hasAccess(to: .automatedMonitoring)) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) if logs.isEmpty { Section { @@ -79,7 +79,7 @@ struct ScheduledReportsView: View { showsCardBackground: false ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { Section("Recent Reports") { ForEach(logs) { log in @@ -96,11 +96,11 @@ struct ScheduledReportsView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Scheduled Reports") } diff --git a/DomainDig/TimelineView.swift b/DomainDig/TimelineView.swift index 8680d15..052b088 100644 --- a/DomainDig/TimelineView.swift +++ b/DomainDig/TimelineView.swift @@ -45,12 +45,12 @@ struct TimelineView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle(domain) .toolbar { ToolbarItemGroup(placement: .topBarTrailing) { @@ -116,8 +116,8 @@ private struct TimelineRow: View { model: .init( title: severity.title, systemImage: "arrow.triangle.2.circlepath", - foregroundColor: severity == .high ? .red : .yellow, - backgroundColor: (severity == .high ? Color.red : .yellow).opacity(0.16) + foregroundColor: severity == .high ? Color(.statusCritical) : Color(.statusWarning), + backgroundColor: (severity == .high ? Color(.statusCritical) : Color(.statusWarning)).opacity(0.16) ) ) } @@ -203,7 +203,7 @@ struct TimelineDiffView: View { } .padding() } - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Compare Snapshots") .navigationBarTitleDisplayMode(.inline) .onAppear { diff --git a/DomainDig/WatchlistView.swift b/DomainDig/WatchlistView.swift index 389ae91..5c5609e 100644 --- a/DomainDig/WatchlistView.swift +++ b/DomainDig/WatchlistView.swift @@ -38,7 +38,7 @@ struct WatchlistView: View { Section("Refresh Progress") { VStack(alignment: .leading, spacing: 8) { ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) - .tint(.cyan) + .tint(Color(.statusInfo)) HStack { Text(viewModel.batchProgressLabel) .font(appDensity.font(.caption)) @@ -59,7 +59,7 @@ struct WatchlistView: View { } .padding(.vertical, 4) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } if viewModel.filteredTrackedDomains.isEmpty { @@ -72,7 +72,7 @@ struct WatchlistView: View { showsCardBackground: false ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { if let limitMessage = FeatureAccessService.trackedDomainLimitMessage(currentCount: viewModel.trackedDomains.count) { Section { @@ -80,7 +80,7 @@ struct WatchlistView: View { .font(appDensity.font(.caption)) .foregroundStyle(.secondary) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } if !pinnedDomains.isEmpty { @@ -94,7 +94,7 @@ struct WatchlistView: View { } .animation(.easeInOut(duration: 0.2), value: viewModel.filteredTrackedDomains.map(\.id)) .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle("Watchlist") .searchable(text: $viewModel.watchlistSearchText, prompt: "Search tracked domains") .toolbar { @@ -215,7 +215,7 @@ struct WatchlistView: View { Section { Text(addDomainError) .font(appDensity.font(.caption)) - .foregroundStyle(.red) + .foregroundStyle(Color(.statusCritical)) } } } @@ -317,14 +317,14 @@ struct WatchlistView: View { } label: { Label("Refresh", systemImage: "arrow.clockwise") } - .tint(.cyan) + .tint(Color(.statusInfo)) Button { viewModel.togglePinned(for: trackedDomain) } label: { Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin") } - .tint(.yellow) + .tint(Color(.statusWarning)) } .swipeActions(edge: .trailing, allowsFullSwipe: false) { Button { @@ -333,7 +333,7 @@ struct WatchlistView: View { } label: { Label("Refresh", systemImage: "arrow.clockwise") } - .tint(.cyan) + .tint(Color(.statusInfo)) if viewModel.canDelete(trackedDomain) { Button(role: .destructive) { @@ -380,7 +380,7 @@ struct WatchlistView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } private var batchSummaryBinding: Binding<BatchSweepSummary?> { @@ -437,7 +437,7 @@ struct WatchlistRowView: View { if trackedDomain.isPinned { Image(systemName: "pin.fill") .font(.caption2) - .foregroundStyle(.yellow) + .foregroundStyle(Color(.statusWarning)) } Text(trackedDomain.domain) .font(appDensity.font(.callout)) @@ -502,7 +502,7 @@ struct WatchlistRowView: View { @ViewBuilder private var statusBadge: some View { if isRefreshing { - AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.6))) + AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))) } else { AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability)) } @@ -516,8 +516,8 @@ struct WatchlistRowView: View { model: .init( title: "Shared", systemImage: "person.2.fill", - foregroundColor: .cyan, - backgroundColor: .cyan.opacity(0.16) + foregroundColor: Color(.statusInfo), + backgroundColor: Color(.statusInfo).opacity(0.16) ) ) } @@ -534,11 +534,11 @@ struct WatchlistRowView: View { let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon" switch trackedDomain.certificateWarningLevel { case .critical: - return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16)) + return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) case .warning: - return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16)) + return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) case .none: - return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)) + return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) } } } @@ -577,7 +577,7 @@ struct TrackedDomainDetailView: View { isRefreshing: viewModel.refreshingTrackedDomainID == liveTrackedDomain.id ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) Section { Button { @@ -652,13 +652,13 @@ struct TrackedDomainDetailView: View { Label(liveTrackedDomain.collaboration?.isShared == true ? "Manage Share" : "Share Domain", systemImage: "person.2") } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) if !liveTrackedDomain.tags.isEmpty { Section("Tags") { TagChipRowView(tags: liveTrackedDomain.tags) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } Section("Monitoring Status") { @@ -672,7 +672,7 @@ struct TrackedDomainDetailView: View { LabeledContent("Queued Alerts", value: "\(liveTrackedDomain.pendingMonitoringAlerts.count)") } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) if let summary = viewModel.latestChangeSummary(for: liveTrackedDomain) { Section("Latest Change Summary") { @@ -718,10 +718,10 @@ struct TrackedDomainDetailView: View { } } } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle(liveTrackedDomain.domain) .preferredColorScheme(.dark) .onChange(of: viewModel.rerunNavigationToken) { _, _ in diff --git a/DomainDig/WorkflowsView.swift b/DomainDig/WorkflowsView.swift index ce889fb..2195af5 100644 --- a/DomainDig/WorkflowsView.swift +++ b/DomainDig/WorkflowsView.swift @@ -24,13 +24,13 @@ struct WorkflowsView: View { .font(appDensity.font(.caption)) .foregroundStyle(.secondary) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } workflowContent } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .refreshable { await viewModel.refreshWorkflowList() } @@ -73,7 +73,7 @@ struct WorkflowsView: View { value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1)) ) - .tint(.cyan) + .tint(Color(.statusInfo)) HStack { Text(viewModel.batchProgressLabel) @@ -91,7 +91,7 @@ struct WorkflowsView: View { } .padding(.vertical, 4) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } if viewModel.workflows.isEmpty { @@ -104,7 +104,7 @@ struct WorkflowsView: View { showsCardBackground: false ) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } else { ForEach(viewModel.workflows) { workflow in NavigationLink { @@ -112,7 +112,7 @@ struct WorkflowsView: View { } label: { WorkflowRowView(workflow: workflow) } - .listRowBackground(Color(.systemGray6).opacity(0.5)) + .listRowBackground(Color(.appSurface)) } .onDelete { offsets in let workflows = offsets.map { viewModel.workflows[$0] } @@ -133,7 +133,7 @@ private struct WorkflowRowView: View { if workflow.collaboration?.isShared == true { Image(systemName: "person.2.fill") .font(.caption2) - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) } Text(workflow.name) .font(appDensity.font(.callout, design: .default, weight: .semibold)) @@ -335,7 +335,7 @@ struct WorkflowDetailView: View { } } .scrollContentBackground(.hidden) - .background(Color.black) + .background(Color(.appBackground)) .navigationTitle(workflow.name) .toolbar { if !workflow.domains.isEmpty { @@ -640,7 +640,7 @@ struct WorkflowBulkAddSheet: View { .foregroundStyle(.primary) Spacer() Image(systemName: selectedDomains.contains(domain) ? "checkmark.circle.fill" : "circle") - .foregroundStyle(selectedDomains.contains(domain) ? .cyan : .secondary) + .foregroundStyle(selectedDomains.contains(domain) ? Color(.statusInfo) : .secondary) } } .buttonStyle(.plain) diff --git a/DomainDigUITests/AccessibilityAuditHarness.swift b/DomainDigUITests/AccessibilityAuditHarness.swift index 9767d95..ca4d12b 100644 --- a/DomainDigUITests/AccessibilityAuditHarness.swift +++ b/DomainDigUITests/AccessibilityAuditHarness.swift @@ -87,7 +87,13 @@ enum AccessibilityAuditHarness { try app.performAccessibilityAudit { issue in let isEnforced = !enforcedAuditTypes.intersection(issue.auditType).isEmpty let marker = isEnforced ? "FAIL" : "report" - findings.append("[\(marker)][\(name(for: issue.auditType))] \(issue.compactDescription)") + // Include the element so the burndown says *what* to fix, not + // just that something is wrong. + let element = issue.element.map { el -> String in + let label = el.label.isEmpty ? el.identifier : el.label + return label.isEmpty ? "\(el.elementType)" : "\"\(label)\"" + } ?? "unknown element" + findings.append("[\(marker)][\(name(for: issue.auditType))] \(issue.compactDescription) — \(element)") // true suppresses the finding, false reports it as a test failure. return !isEnforced } diff --git a/DomainDigUITests/AccessibilityAuditTests.swift b/DomainDigUITests/AccessibilityAuditTests.swift index 6a90bac..63ae584 100644 --- a/DomainDigUITests/AccessibilityAuditTests.swift +++ b/DomainDigUITests/AccessibilityAuditTests.swift @@ -17,7 +17,20 @@ final class AccessibilityAuditTests: XCTestCase { // MARK: Per-screen audits func testInspectScreen() throws { - try auditRootTab("Inspect") + let app = AccessibilityAuditHarness.launch() + app.selectRootTab("Inspect") + + // Type a domain so the Run button is enabled. A disabled control has no + // contrast requirement under WCAG 1.4.3, but the audit still flags it, + // so auditing the empty state would report a false positive forever. + let field = app.textFields.firstMatch + if field.waitForExistence(timeout: 5) { + field.tap() + field.typeText("example.com") + } + + let audited = try AccessibilityAuditHarness.audit(app, screen: "inspect", test: self) + try XCTSkipUnless(audited, "Audit did not complete in time for Inspect") } func testDashboardScreen() throws { diff --git a/DomainDigWidget/DomainDigPortfolioWidget.swift b/DomainDigWidget/DomainDigPortfolioWidget.swift index bc88ac0..a3335c8 100644 --- a/DomainDigWidget/DomainDigPortfolioWidget.swift +++ b/DomainDigWidget/DomainDigPortfolioWidget.swift @@ -91,9 +91,9 @@ struct DomainDigWidgetView: View { Spacer(minLength: 0) HStack(spacing: 10) { - countPill(data.healthyCount, .green) - countPill(data.warningCount, .orange) - countPill(data.criticalCount, .red) + countPill(data.healthyCount, Color(.statusPositive)) + countPill(data.warningCount, Color(.statusWarning)) + countPill(data.criticalCount, Color(.statusCritical)) } } } @@ -121,10 +121,10 @@ struct DomainDigWidgetView: View { } HStack(spacing: 12) { - summaryStat(data.healthyCount, "Healthy", .green) - summaryStat(data.warningCount, "Warning", .orange) - summaryStat(data.criticalCount, "Critical", .red) - summaryStat(data.expiringSoonCount, "Expiring", .yellow) + summaryStat(data.healthyCount, "Healthy", Color(.statusPositive)) + summaryStat(data.warningCount, "Warning", Color(.statusWarning)) + summaryStat(data.criticalCount, "Critical", Color(.statusCritical)) + summaryStat(data.expiringSoonCount, "Expiring", Color(.statusWarning)) } Divider() @@ -180,9 +180,9 @@ struct DomainDigWidgetView: View { private func color(for status: DomainDigWidgetStatus) -> Color { switch status { - case .healthy: return .green - case .warning: return .orange - case .critical: return .red + case .healthy: return Color(.statusPositive) + case .warning: return Color(.statusWarning) + case .critical: return Color(.statusCritical) } } } diff --git a/DomainDigWidget/SweepLiveActivity.swift b/DomainDigWidget/SweepLiveActivity.swift index b99a070..b96f8bf 100644 --- a/DomainDigWidget/SweepLiveActivity.swift +++ b/DomainDigWidget/SweepLiveActivity.swift @@ -24,7 +24,7 @@ struct SweepLiveActivity: Widget { DynamicIslandExpandedRegion(.bottom) { VStack(alignment: .leading, spacing: 4) { ProgressView(value: context.state.fractionComplete) - .tint(.cyan) + .tint(Color(.statusInfo)) if let current = context.state.currentDomain { Text(current) .font(.caption2) @@ -35,7 +35,7 @@ struct SweepLiveActivity: Widget { } } compactLeading: { Image(systemName: "arrow.trianglehead.2.clockwise") - .foregroundStyle(.cyan) + .foregroundStyle(Color(.statusInfo)) } compactTrailing: { Text("\(context.state.completed)/\(context.state.total)") .font(.caption2) @@ -43,7 +43,7 @@ struct SweepLiveActivity: Widget { } minimal: { ProgressView(value: context.state.fractionComplete) .progressViewStyle(.circular) - .tint(.cyan) + .tint(Color(.statusInfo)) } } } @@ -66,7 +66,7 @@ private struct SweepActivityBannerView: View { } ProgressView(value: context.state.fractionComplete) - .tint(.cyan) + .tint(Color(.statusInfo)) HStack { if let current = context.state.currentDomain { @@ -83,12 +83,12 @@ private struct SweepActivityBannerView: View { if context.state.changed > 0 { Text("\(context.state.changed) changed") .font(.caption2) - .foregroundStyle(.orange) + .foregroundStyle(Color(.statusWarning)) } if context.state.warnings > 0 { Text("\(context.state.warnings) warnings") .font(.caption2) - .foregroundStyle(.red) + .foregroundStyle(Color(.statusCritical)) } } } diff --git a/Shared/Colors.xcassets/AccentFill.colorset/Contents.json b/Shared/Colors.xcassets/AccentFill.colorset/Contents.json new file mode 100644 index 0000000..ae20bb8 --- /dev/null +++ b/Shared/Colors.xcassets/AccentFill.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0xFF", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x40", + "blue": "0xDD", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0xCC", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x33", + "blue": "0xCC", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/AppBackground.colorset/Contents.json b/Shared/Colors.xcassets/AppBackground.colorset/Contents.json new file mode 100644 index 0000000..ac109ac --- /dev/null +++ b/Shared/Colors.xcassets/AppBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0xFF", + "green": "0xFF", + "blue": "0xFF", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0x00", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/AppOnAccent.colorset/Contents.json b/Shared/Colors.xcassets/AppOnAccent.colorset/Contents.json new file mode 100644 index 0000000..ac109ac --- /dev/null +++ b/Shared/Colors.xcassets/AppOnAccent.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0xFF", + "green": "0xFF", + "blue": "0xFF", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0x00", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/AppSeparator.colorset/Contents.json b/Shared/Colors.xcassets/AppSeparator.colorset/Contents.json new file mode 100644 index 0000000..3f5960a --- /dev/null +++ b/Shared/Colors.xcassets/AppSeparator.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0xC6", + "green": "0xC6", + "blue": "0xC8", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x38", + "green": "0x38", + "blue": "0x3A", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xA0", + "green": "0xA0", + "blue": "0xA3", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x54", + "green": "0x54", + "blue": "0x56", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/AppSurface.colorset/Contents.json b/Shared/Colors.xcassets/AppSurface.colorset/Contents.json new file mode 100644 index 0000000..94ae3c7 --- /dev/null +++ b/Shared/Colors.xcassets/AppSurface.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0xF2", + "green": "0xF2", + "blue": "0xF7", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x0E", + "green": "0x0E", + "blue": "0x0F", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xEB", + "green": "0xEB", + "blue": "0xF0", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x16", + "green": "0x16", + "blue": "0x18", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/AppSurfaceElevated.colorset/Contents.json b/Shared/Colors.xcassets/AppSurfaceElevated.colorset/Contents.json new file mode 100644 index 0000000..775eafb --- /dev/null +++ b/Shared/Colors.xcassets/AppSurfaceElevated.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0xE5", + "green": "0xE5", + "blue": "0xEA", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x1A", + "green": "0x1A", + "blue": "0x1C", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xDC", + "green": "0xDC", + "blue": "0xE1", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x23", + "green": "0x23", + "blue": "0x26", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/Contents.json b/Shared/Colors.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Shared/Colors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Colors.xcassets/StatusCritical.colorset/Contents.json b/Shared/Colors.xcassets/StatusCritical.colorset/Contents.json new file mode 100644 index 0000000..8f97cd9 --- /dev/null +++ b/Shared/Colors.xcassets/StatusCritical.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0xB3", + "green": "0x26", + "blue": "0x1E", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xFF", + "green": "0x69", + "blue": "0x61", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x8C", + "green": "0x1D", + "blue": "0x16", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xFF", + "green": "0x9A", + "blue": "0x94", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/StatusInfo.colorset/Contents.json b/Shared/Colors.xcassets/StatusInfo.colorset/Contents.json new file mode 100644 index 0000000..9d1e3b9 --- /dev/null +++ b/Shared/Colors.xcassets/StatusInfo.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0xFF", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x4D", + "green": "0xA3", + "blue": "0xFF", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x00", + "green": "0x00", + "blue": "0xCC", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x7F", + "green": "0xBF", + "blue": "0xFF", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/StatusNeutral.colorset/Contents.json b/Shared/Colors.xcassets/StatusNeutral.colorset/Contents.json new file mode 100644 index 0000000..9e7bb36 --- /dev/null +++ b/Shared/Colors.xcassets/StatusNeutral.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0x5A", + "green": "0x5A", + "blue": "0x5F", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xA1", + "green": "0xA1", + "blue": "0xA6", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x3A", + "green": "0x3A", + "blue": "0x3C", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xC7", + "green": "0xC7", + "blue": "0xCC", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/StatusPositive.colorset/Contents.json b/Shared/Colors.xcassets/StatusPositive.colorset/Contents.json new file mode 100644 index 0000000..22ce5e5 --- /dev/null +++ b/Shared/Colors.xcassets/StatusPositive.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0x14", + "green": "0x6C", + "blue": "0x2E", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x30", + "green": "0xD1", + "blue": "0x58", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x0B", + "green": "0x50", + "blue": "0x23", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x5B", + "green": "0xE5", + "blue": "0x84", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Shared/Colors.xcassets/StatusWarning.colorset/Contents.json b/Shared/Colors.xcassets/StatusWarning.colorset/Contents.json new file mode 100644 index 0000000..cc79e86 --- /dev/null +++ b/Shared/Colors.xcassets/StatusWarning.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors": [ + { + "idiom": "universal", + "color": { + "color-space": "srgb", + "components": { + "red": "0x7A", + "green": "0x56", + "blue": "0x00", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xFF", + "green": "0xD6", + "blue": "0x0A", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0x5C", + "green": "0x41", + "blue": "0x00", + "alpha": "1.000" + } + } + }, + { + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + }, + { + "appearance": "contrast", + "value": "high" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0xFF", + "green": "0xE5", + "blue": "0x66", + "alpha": "1.000" + } + } + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} |
