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:
Trey t
2026-02-11 09:27:23 -06:00
parent e9c15d70b1
commit d63d311cab
77 changed files with 982 additions and 263 deletions

View File

@@ -115,14 +115,16 @@ struct AchievementsListView: View {
.frame(width: 64, height: 64)
Image(systemName: selectedSport?.iconName ?? "trophy.fill")
.font(.system(size: 28))
.font(.title2)
.foregroundStyle(earned > 0 ? completedGold : accentColor)
.accessibilityHidden(true)
}
VStack(alignment: .leading, spacing: Theme.Spacing.xs) {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text("\(earned)")
.font(.system(size: 36, weight: .bold, design: .rounded))
.font(.system(.largeTitle, design: .rounded).weight(.bold))
.monospacedDigit()
.foregroundStyle(earned > 0 ? completedGold : Theme.textPrimary(colorScheme))
Text("/ \(total)")
.font(.title2)
@@ -174,7 +176,7 @@ struct AchievementsListView: View {
color: Theme.warmOrange,
isSelected: selectedSport == nil
) {
withAnimation(Theme.Animation.spring) {
Theme.Animation.withMotion(Theme.Animation.spring) {
selectedSport = nil
}
}
@@ -187,7 +189,7 @@ struct AchievementsListView: View {
color: sport.themeColor,
isSelected: selectedSport == sport
) {
withAnimation(Theme.Animation.spring) {
Theme.Animation.withMotion(Theme.Animation.spring) {
selectedSport = sport
}
}
@@ -287,6 +289,8 @@ struct SportFilterButton: View {
}
}
.buttonStyle(.plain)
.accessibilityValue(isSelected ? "Selected" : "Not selected")
.accessibilityAddTraits(isSelected ? .isSelected : [])
}
}
@@ -318,8 +322,9 @@ struct AchievementCard: View {
}
Image(systemName: achievement.definition.iconName)
.font(.system(size: 28))
.font(.title2)
.foregroundStyle(badgeIconColor)
.accessibilityHidden(true)
if !achievement.isEarned {
Circle()
@@ -329,6 +334,7 @@ struct AchievementCard: View {
Image(systemName: "lock.fill")
.font(.subheadline)
.foregroundStyle(.white)
.accessibilityHidden(true)
}
}
@@ -346,6 +352,7 @@ struct AchievementCard: View {
HStack(spacing: 4) {
Image(systemName: "checkmark.seal.fill")
.font(.caption)
.accessibilityHidden(true)
if let earnedAt = achievement.earnedAt {
Text(earnedAt.formatted(date: .abbreviated, time: .omitted))
} else {
@@ -376,6 +383,7 @@ struct AchievementCard: View {
}
.shadow(color: achievement.isEarned ? completedGold.opacity(0.3) : Theme.cardShadow(colorScheme), radius: achievement.isEarned ? 8 : 5, y: 2)
.opacity(achievement.isEarned ? 1.0 : 0.7)
.accessibilityElement(children: .combine)
}
private var badgeBackgroundColor: Color {
@@ -492,8 +500,9 @@ struct AchievementDetailSheet: View {
}
Image(systemName: achievement.definition.iconName)
.font(.system(size: 56))
.font(.largeTitle)
.foregroundStyle(badgeIconColor)
.accessibilityHidden(true)
if !achievement.isEarned {
Circle()
@@ -501,8 +510,9 @@ struct AchievementDetailSheet: View {
.frame(width: 120, height: 120)
Image(systemName: "lock.fill")
.font(.system(size: 24))
.font(.title3)
.foregroundStyle(.white)
.accessibilityHidden(true)
}
}
@@ -538,8 +548,9 @@ struct AchievementDetailSheet: View {
if achievement.isEarned {
VStack(spacing: 8) {
Image(systemName: "checkmark.seal.fill")
.font(.system(size: 32))
.font(.title)
.foregroundStyle(completedGold)
.accessibilityHidden(true)
if let earnedAt = achievement.earnedAt {
Text("Earned on \(earnedAt.formatted(date: .long, time: .omitted))")
@@ -575,6 +586,7 @@ struct AchievementDetailSheet: View {
if let sport = achievement.definition.sport {
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: sport.iconName)
.accessibilityLabel(sport.displayName)
Text(sport.displayName)
}
.font(.subheadline)