feat: add WCAG AA accessibility app-wide, fix CloudKit container config, remove debug logs
- Add VoiceOver labels, hints, and element grouping across all 60+ views - Add Reduce Motion support (Theme.Animation.prefersReducedMotion) to all animations - Replace fixed font sizes with semantic Dynamic Type styles - Hide decorative elements from VoiceOver with .accessibilityHidden(true) - Add .minimumHitTarget() modifier ensuring 44pt touch targets - Add AccessibilityAnnouncer utility for VoiceOver announcements - Improve color contrast values in Theme.swift for WCAG AA compliance - Extract CloudKitContainerConfig for explicit container identity - Remove PostHog debug console log from AnalyticsManager Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -308,7 +308,7 @@ struct TripOptionsView: View {
|
||||
hasAppliedDemoSelection = true
|
||||
// Auto-select "Most Games" sort after a delay
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
|
||||
sortOption = DemoConfig.demoSortOption
|
||||
}
|
||||
}
|
||||
@@ -329,7 +329,7 @@ struct TripOptionsView: View {
|
||||
Menu {
|
||||
ForEach(TripSortOption.allCases) { option in
|
||||
Button {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
|
||||
sortOption = option
|
||||
}
|
||||
} label: {
|
||||
@@ -345,6 +345,7 @@ struct TripOptionsView: View {
|
||||
.font(.subheadline)
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.caption)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.padding(.horizontal, 16)
|
||||
@@ -397,6 +398,7 @@ struct TripOptionsView: View {
|
||||
.contentTransition(.identity)
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.caption2)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.foregroundStyle(paceFilter == .all ? Theme.textPrimary(colorScheme) : Theme.warmOrange)
|
||||
.padding(.horizontal, 12)
|
||||
@@ -420,12 +422,12 @@ struct TripOptionsView: View {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(CitiesFilter.allCases) { filter in
|
||||
Button {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
|
||||
citiesFilter = filter
|
||||
}
|
||||
} label: {
|
||||
Text(filter.displayName)
|
||||
.font(.system(size: 13, weight: citiesFilter == filter ? .semibold : .medium))
|
||||
.font(.caption.weight(citiesFilter == filter ? .semibold : .medium))
|
||||
.foregroundStyle(citiesFilter == filter ? .white : Theme.textPrimary(colorScheme))
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
@@ -446,15 +448,16 @@ struct TripOptionsView: View {
|
||||
private var emptyFilterState: some View {
|
||||
VStack(spacing: Theme.Spacing.md) {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||
.font(.system(size: 48))
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text("No routes match your filters")
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
Theme.Animation.withMotion {
|
||||
citiesFilter = .noLimit
|
||||
paceFilter = .all
|
||||
}
|
||||
@@ -524,6 +527,7 @@ struct TripOptionCard: View {
|
||||
.font(.caption2)
|
||||
}
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(uniqueCities.last ?? "")
|
||||
.font(.subheadline)
|
||||
@@ -560,7 +564,7 @@ struct TripOptionCard: View {
|
||||
// AI-generated description (after stats)
|
||||
if let description = aiDescription {
|
||||
Text(description)
|
||||
.font(.system(size: 13, weight: .regular))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.transition(.opacity)
|
||||
@@ -578,8 +582,9 @@ struct TripOptionCard: View {
|
||||
|
||||
// Right: Chevron
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.padding(Theme.Spacing.md)
|
||||
.background(Theme.cardBackground(colorScheme))
|
||||
@@ -607,7 +612,7 @@ struct TripOptionCard: View {
|
||||
let input = RouteDescriptionInput(from: option, games: games)
|
||||
|
||||
if let description = await RouteDescriptionGenerator.shared.generateDescription(for: input) {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
|
||||
aiDescription = description
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user