diff options
| author | Christian Cleberg <[email protected]> | 2026-07-20 20:25:01 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-20 21:03:10 -0500 |
| commit | 9e58a36915bb1fe8cf88423c9519c0c12dbbf2e2 (patch) | |
| tree | a9397c168adcdb63deda80440511063ef57732f4 /DomainDig/ContentView.swift | |
| parent | 2c608ed9cb11f3e835f577994b5627b9b7910f4f (diff) | |
| download | domain-dig-9e58a36915bb1fe8cf88423c9519c0c12dbbf2e2.tar.gz domain-dig-9e58a36915bb1fe8cf88423c9519c0c12dbbf2e2.tar.bz2 domain-dig-9e58a36915bb1fe8cf88423c9519c0c12dbbf2e2.zip | |
fix(a11y): rebalance the light palette so hues survive
Reported as "colors seem muted and hard to see on light mode", and
correct. The light palette optimised contrast and produced mud: #7A5600
reads olive rather than amber, #146C2E bottle-dark rather than green.
Contrast passed while the UI got harder to read, because hue identity is
what distinguishes warning from critical at a glance.
Two causes, both fixed.
Every foreground was required to clear 4.5:1 against its own 16% badge
tint — the harshest surface it ever sits on — which pushed each colour
about 20% darker than the common case needed. Most of what is actually on
screen is plain text on a card, with far more headroom. The fill is now
decoupled from the foreground: AppStatusTone carries a foreground and a
surface authored independently, with matching …Surface colorsets, so a
foreground no longer has to survive a wash of itself. Every status
foreground is now fully saturated.
And warning was yellow. Yellow cannot stay yellow at a lightness low
enough to pass 4.5:1 on white — it becomes olive. That is colorimetric,
not a tuning problem. Warning is now orange: #AD5100 light, #FF9F0A dark.
New light values: positive #008035, warning #AD5100, critical #CC0700.
Worst-case ratios 4.54–6.76 across page, card, and surface in both
schemes. Audit findings are unchanged — light 21, dark 18 — so the
vividness costs nothing.
Also picks up a literal .blue missed in phase 1: DomainDiffItem's
low-severity change colour, which the phase 1 sweep did not cover because
its pattern listed only cyan/yellow/green/red/orange/pink.
Diffstat (limited to 'DomainDig/ContentView.swift')
| -rw-r--r-- | DomainDig/ContentView.swift | 60 |
1 files changed, 30 insertions, 30 deletions
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift index efaaf05..47512c8 100644 --- a/DomainDig/ContentView.swift +++ b/DomainDig/ContentView.swift @@ -1029,21 +1029,21 @@ 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) : Color(.statusPositive)) + .foregroundStyle(summary.hasChanges ? severityTone(summary.severity).foreground : Color(.statusPositive)) Spacer() Text(summary.severity.title.uppercased()) .font(appDensity.font(.caption2)) - .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .secondary) + .foregroundStyle(summary.hasChanges ? severityTone(summary.severity).foreground : Color(.appTextSecondary)) .padding(.horizontal, 8) .padding(.vertical, 4) - .background((summary.hasChanges ? severityColor(summary.severity) : .secondary).opacity(0.16)) + .background((summary.hasChanges ? severityTone(summary.severity) : AppStatusTone.neutral).surface) .clipShape(Capsule()) Text(summary.impactClassification.title.uppercased()) .font(appDensity.font(.caption2)) - .foregroundStyle(summary.impactClassification.color) + .foregroundStyle(summary.impactClassification.tone.foreground) .padding(.horizontal, 8) .padding(.vertical, 4) - .background(summary.impactClassification.color.opacity(0.16)) + .background(summary.impactClassification.tone.surface) .clipShape(Capsule()) Text(summary.generatedAt, style: .time) .font(appDensity.font(.caption2)) @@ -1096,14 +1096,14 @@ struct DomainChangeSummaryView: View { } } - private func severityColor(_ severity: ChangeSeverity) -> Color { + private func severityTone(_ severity: ChangeSeverity) -> AppStatusTone { switch severity { case .low: - return .secondary + return .neutral case .medium: - return Color(.statusWarning) + return .warning case .high: - return Color(.statusCritical) + return .critical } } @@ -1173,10 +1173,10 @@ struct DomainDiffView: View { Spacer() Text("\(item.changeType.marker) \(item.severity.title) • \(changeLabel(for: item.changeType))") .font(.system(.caption2, design: .monospaced)) - .foregroundStyle(changeColor(for: item)) + .foregroundStyle(changeTone(for: item).foreground) .padding(.horizontal, 8) .padding(.vertical, 4) - .background(changeColor(for: item).opacity(0.16)) + .background(changeTone(for: item).surface) .clipShape(Capsule()) } @@ -1205,7 +1205,7 @@ struct DomainDiffView: View { } } .padding(10) - .background(item.hasChanges ? changeColor(for: item).opacity(0.08) : Color(.appSurface)) + .background(item.hasChanges ? changeTone(for: item).surface : Color(.appSurface)) .cornerRadius(8) } } label: { @@ -1249,18 +1249,18 @@ struct DomainDiffView: View { } } - private func changeColor(for item: DomainDiffItem) -> Color { + private func changeTone(for item: DomainDiffItem) -> AppStatusTone { if item.changeType == .unchanged { - return .secondary + return .neutral } switch item.severity { case .low: - return .blue + return .info case .medium: - return Color(.statusWarning) + return .warning case .high: - return Color(.statusCritical) + return .critical } } @@ -1337,7 +1337,7 @@ struct DomainSectionView: View { CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) { if let trackedDomain { HStack(spacing: 8) { - AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16))) + AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))) Button { onTogglePinned() } label: { @@ -2186,9 +2186,9 @@ struct EmailSectionView: View { private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel { switch row.statusTone { case .success: - return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) + return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface)) case .warning: - return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) + return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface)) case .failure: return .init(title: row.status, systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) case .primary, .secondary: @@ -2345,11 +2345,11 @@ struct NetworkSectionView: View { private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel { switch row.statusTone { case .success: - return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface)) case .warning: - return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface)) case .failure: - return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface)) case .primary, .secondary: return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) } @@ -2396,11 +2396,11 @@ struct PortRowsView: View { private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel { switch row.statusTone { case .success: - return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositive).opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface)) case .warning: - return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarningSurface)) case .failure: - return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) + return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCriticalSurface)) case .primary, .secondary: return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)) } @@ -3754,11 +3754,11 @@ private struct DataImportPreviewSheet: View { } extension ChangeImpactClassification { - var color: Color { + var tone: AppStatusTone { switch self { - case .informational: return .secondary - case .warning: return Color(.statusWarning) - case .critical: return Color(.statusCritical) + case .informational: return .neutral + case .warning: return .warning + case .critical: return .critical } } } |
