high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Banner displays current tier name, tier icon, and award period label simultaneously
Tier-reveal animation plays exactly once when badge-bloc emits a new-tier-awarded event; subsequent renders show the static banner
All tier colour variants (e.g., bronze, silver, gold) meet WCAG 2.2 AA contrast ratio (4.5:1 text, 3:1 for large/icon elements) against the banner background
Semantics widget provides a label of the form 'Recognition tier: [Tier Name], awarded for [Period]' readable by VoiceOver and TalkBack
Widget gracefully handles a null or loading tier state without throwing (shows a skeleton or empty state)
Animation is skippable / reduced when the device has 'Reduce Motion' enabled (respects MediaQuery.disableAnimations)
Banner is a stateless or BlocConsumer widget — no local mutable state beyond AnimationController lifecycle
Widget integrates without layout overflow on screens 320px wide and above

Technical Requirements

frameworks
Flutter
BLoC
data models
RecognitionTier
AwardPeriod
performance requirements
Tier-reveal animation runs at 60fps with no dropped frames
AnimationController disposed correctly to prevent memory leaks
ui components
AnimationController + CurvedAnimation for tier-reveal
AnimatedOpacity or SlideTransition for reveal effect
Semantics widget for screen reader label
Design token color palette for tier variants
SvgPicture or Image.asset for tier icon

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as a BlocConsumer or BlocBuilder widget listening to the badge-bloc tier state. Use a single AnimationController managed via a StatefulWidget (or a custom hook if Riverpod is used elsewhere in this epic) to drive the reveal animation. Gate the animation trigger with a boolean flag (e.g., isNewlyAwarded from the bloc state) and reset after first play using addStatusListener to detect AnimationStatus.completed. For 'Reduce Motion' support, check MediaQuery.of(context).disableAnimations and skip animation if true.

Define tier colour tokens (bronzeColor, silverColor, goldColor, etc.) in the design token file — never hardcode hex values inline. Keep the widget purely presentational; the parent is responsible for providing the TierState via BLoC.

Testing Requirements

Write widget tests (flutter_test) covering: (1) static banner renders tier name, icon, and period correctly for each tier variant, (2) animation triggers on new-tier-awarded state and does not replay on rebuild, (3) null/loading tier state renders without exception, (4) Semantics label matches expected string. Use tester.pump() and tester.pumpAndSettle() to drive animation assertions. Test that MediaQuery.disableAnimations: true suppresses the animation. Perform manual contrast check for each tier colour variant using a contrast analyser.

Minimum 75% widget coverage.

Component
Recognition Tier Banner
ui low
Epic Risks (2)
medium impact medium prob integration

The badge-earned-celebration overlay must appear within 2 seconds of the triggering activity being saved, but badge evaluation runs server-side in an edge function triggered by a database webhook. Network latency, edge function cold start, and Supabase Realtime delivery delays could cause the overlay to appear late or not at all, breaking the motivational loop.

Mitigation & Contingency

Mitigation: Implement an optimistic UI path: after activity save, badge-bloc immediately checks whether any badge thresholds are crossed client-side using cached stats and badge definitions, showing the overlay speculatively before server confirmation. The server result then reconciles. Subscribe to Supabase Realtime on the earned_badges table for authoritative confirmation.

Contingency: If Realtime delivery is unreliable in production, add a polling fallback: badge-bloc polls for new earned badges 3 seconds after an activity save and shows the overlay if a new record is detected, accepting up to 5-second latency as a fallback SLA.

high impact low prob technical

The celebration overlay uses animation for positive reinforcement, but motion sensitivity (prefers-reduced-motion) and screen reader users require a non-animated or text-only alternative. Failing to handle this risks excluding Blindeforbundet users or triggering vestibular discomfort for motion-sensitive volunteers.

Mitigation & Contingency

Mitigation: Check MediaQuery.disableAnimations in badge-earned-celebration-overlay and skip animation entirely when true, showing a static card instead. Add an ExcludeSemantics wrapper around the decorative animation widget and a separate Semantics node with a live region announcement of the badge name and congratulatory message.

Contingency: If accessibility issues are identified in TestFlight testing with Blindeforbundet's test group, fast-track a patch that defaults to the static card path and gates the animation behind a user preference setting in notification preferences.