Milestone Celebration Generator
Generate a production milestone celebration system with confetti particle animations via CAEmitterLayer, achievement badge views with locked/unlocked states, a celebration overlay with spring animations, haptic feedback, and optional shareable achievement cards — all triggered automatically when users hit key thresholds.
When This Skill Activates
Use this skill when the user:
- Asks to "add celebrations" or "celebrate achievements"
- Wants "confetti animation" or "particle effects for milestones"
- Mentions "achievement badges" or "badge unlock animation"
- Asks about "milestone rewards" or "progress milestones"
- Wants to "celebrate achievement" or "trigger celebration on threshold"
- Asks about "level-up animation" or "gamification celebrations"
Pre-Generation Checks
1. Project Context Detection
- Check Swift version (requires Swift 5.9+)
- Check deployment target (iOS 17+ / macOS 14+ for
@Observable, spring animations,sensoryFeedback) - Check for
UIKitavailability (iOS —CAEmitterLayerfor confetti, haptics) - Identify source file locations
2. Conflict Detection
Search for existing celebration/animation code:
Glob: **/*Confetti*.swift, **/*Celebration*.swift, **/*Achievement*.swift, **/*Milestone*.swift, **/*Badge*.swift Grep: "CAEmitterLayer" or "CAEmitterCell" or "UINotificationFeedbackGenerator" or "confetti" or "celebration"
If existing celebration or gamification library found (e.g., custom confetti, third-party particle libraries):
- Ask if user wants to replace or integrate with it
- If keeping, advise on best practices instead of generating
3. Platform Detection
Determine if generating for iOS (UIKit-based confetti + haptics) or macOS (Core Animation confetti, no haptics) or both (cross-platform with feature gates).
Configuration Questions
Ask user via AskUserQuestion:
-
Celebration type? (multi-select)
- Confetti burst (full-screen particle animation)
- Badge unlock (icon reveal with glow animation)
- Level-up (progress ring fill + title transition)
- Custom (user defines their own celebration style)
-
Haptic feedback?
- Yes — success haptic on celebration trigger (recommended)
- No — silent celebrations only
-
Shareable achievement card?
- Yes — render achievement as image for social sharing via
ShareLink - No — in-app celebration only
- Yes — render achievement as image for social sharing via
-
Sound effects?
- Yes — play system sound or bundled audio on celebration
- No — visual only
Generation Process
Step 1: Read Templates
Read templates.md for production Swift code.
Step 2: Create Core Files
Generate these files:
Milestone.swift— Model with id, title, description, threshold, icon, unlock state.Codable+Sendable.MilestoneTracker.swift—@Observableclass tracking progress toward milestones, checking thresholds, triggering celebrations. Persists unlock state viaUserDefaultsor file storage.ConfettiView.swift—UIViewRepresentablewrappingCAEmitterLayerfor confetti particle animation. Configurable colors, duration, density. Respects Reduce Motion.
Step 3: Create UI Files
CelebrationOverlay.swift— Full-screen overlay combining confetti + badge reveal + congratulations message. Auto-dismisses after configurable duration. UseswithAnimation(.spring).MilestoneBadgeView.swift— Individual badge view with locked/unlocked states, SF Symbol icon, progress ring for partial progress.MilestoneCollectionView.swift— Grid layout of all milestones showing locked/unlocked state with progress indicators.
Step 4: Create Optional Files
Based on configuration:
HapticManager.swift— If haptic feedback selected (thin wrapper aroundUINotificationFeedbackGenerator/UIImpactFeedbackGenerator)ShareableMilestoneCard.swift— If shareable selected (renders milestone as image viaImageRenderer+ShareLink)
Step 5: Determine File Location
Check project structure:
- If
Sources/exists ->Sources/MilestoneCelebration/ - If
App/exists ->App/MilestoneCelebration/ - Otherwise ->
MilestoneCelebration/
Output Format
After generation, provide:
Files Created
MilestoneCelebration/ ├── Milestone.swift # Model with threshold, icon, unlock state ├── MilestoneTracker.swift # @Observable tracker with persistence ├── ConfettiView.swift # CAEmitterLayer confetti animation ├── CelebrationOverlay.swift # Full-screen celebration overlay ├── MilestoneBadgeView.swift # Badge with locked/unlocked + progress ring ├── MilestoneCollectionView.swift # Grid of all milestones ├── HapticManager.swift # Celebration haptics (optional) └── ShareableMilestoneCard.swift # Achievement share card (optional)
Integration Steps
Trigger celebration on threshold:
swiftstruct WorkoutCompleteView: View { @State private var tracker = MilestoneTracker() @State private var celebratingMilestone: Milestone? var body: some View { VStack { // ... workout summary content ... Button("Save Workout") { saveWorkout() let newCount = totalWorkouts + 1 if let milestone = tracker.checkThreshold(value: newCount, category: .workouts) { celebratingMilestone = milestone } } } .overlay { if let milestone = celebratingMilestone { CelebrationOverlay(milestone: milestone) { celebratingMilestone = nil } } } } }
Badge collection screen:
swiftstruct ProfileView: View { @State private var tracker = MilestoneTracker() var body: some View { NavigationStack { ScrollView { MilestoneCollectionView( milestones: tracker.allMilestones, columns: 3 ) } .navigationTitle("Achievements") } } }
Share an achievement:
swiftstruct MilestoneDetailView: View { let milestone: Milestone @State private var showShareCard = false var body: some View { VStack { MilestoneBadgeView(milestone: milestone, size: .large) Text(milestone.title).font(.title2.bold()) Text(milestone.milestoneDescription).foregroundStyle(.secondary) if milestone.isUnlocked { Button("Share Achievement") { showShareCard = true } .buttonStyle(.borderedProminent) } } .sheet(isPresented: $showShareCard) { ShareableMilestoneCard(milestone: milestone, brandName: "FitApp") } } }
Testing
swift@Test func milestoneUnlocksAtThreshold() { let tracker = MilestoneTracker(store: InMemoryMilestoneStore()) let milestone = Milestone( id: "first-10", title: "First 10", milestoneDescription: "Complete 10 workouts", threshold: 10, iconName: "flame.fill" ) tracker.register(milestone) let result = tracker.checkThreshold(value: 10, for: milestone.id) #expect(result != nil) #expect(result?.isUnlocked == true) #expect(result?.unlockedDate != nil) } @Test func milestoneDoesNotUnlockBelowThreshold() { let tracker = MilestoneTracker(store: InMemoryMilestoneStore()) let milestone = Milestone( id: "first-10", title: "First 10", milestoneDescription: "Complete 10 workouts", threshold: 10, iconName: "flame.fill" ) tracker.register(milestone) let result = tracker.checkThreshold(value: 9, for: milestone.id) #expect(result == nil) } @Test func confettiRespectsReduceMotion() { // When Reduce Motion is enabled, ConfettiView should not emit particles let config = ConfettiConfiguration(reduceMotionOverride: true) #expect(config.shouldAnimate == false) } @Test func celebrationOverlayAutoDismisses() async throws { // Verify overlay dismisses after the configured duration let expectation = XCTestExpectation(description: "Dismissed") let duration: TimeInterval = 0.5 // Test that the onDismiss callback fires after duration }
Common Patterns
Trigger Celebration on Threshold
Best for: fitness, learning, productivity apps.
- Define milestones with numeric thresholds (10 workouts, 100 pages read, 7-day streak)
MilestoneTrackerchecks values against registered milestones- Celebration fires only on the first crossing — subsequent calls are no-ops
- Persist unlock state so celebrations do not repeat
Badge Collection View
Best for: gamification, loyalty, progression systems.
- Grid of all milestones: locked ones are dimmed/grayscale, unlocked ones are vibrant
- Progress ring shows how close user is to unlocking each badge
- Tapping a badge shows detail view with full description and share option
- Counter showing "12 of 20 unlocked" at the top
Share Achievement
Best for: social apps, fitness, competitions.
- Render the unlocked badge + title + metric as a shareable image
- Use
ImageRendererat@2xscale for Retina quality - Include app branding and optional QR code for deep linking
- Integrate with
ShareLinkfor native share sheet
Gotchas
CAEmitterLayer Performance on Older Devices
- Confetti with many particles (200+) can drop frames on A11 and older chips
- Cap
birthRateat 50-80 for smooth 60fps on most devices - Use
emitterCellswith only 3-5 distinct shapes to reduce GPU draw calls - Stop emission after burst duration — do not leave the emitter running indefinitely
- Profile with Instruments (Core Animation template) if users report frame drops
Reduce Motion Accessibility
- Always check
UIAccessibility.isReduceMotionEnabledbefore starting particle animations - When Reduce Motion is on: skip confetti, show a static badge reveal or a simple fade-in instead
- Use
@Environment(\.accessibilityReduceMotion)in SwiftUI for reactive updates - Provide an equally rewarding experience without animation — the badge reveal and message still appear
Haptic Feedback on Non-Supporting Devices
UINotificationFeedbackGeneratoris a no-op on devices without a Taptic Engine (e.g., iPod touch, iPad)- Always guard with
CHHapticEngine.capabilitiesForHardware().supportsHapticsbefore preparing generators - On macOS, haptics are not available — gate with
#if canImport(UIKit)and#if os(iOS) - Use
.sensoryFeedback(.success, trigger:)modifier on iOS 17+ for a simpler SwiftUI-native approach
Sound Effect Considerations
- Use
AudioServicesPlaySystemSoundfor lightweight celebration sounds (no audio session needed) - Respect the device silent/mute switch —
AudioServicesPlaySystemSoundhonors it automatically - For custom sounds, keep files under 30 seconds and use CAF or WAV format
- Do not play sounds when Reduce Motion is enabled (some users enable it for sensory reasons)
Persistence Edge Cases
- If the app is terminated between unlocking a milestone and persisting it, the user may see the celebration again
- Use
UserDefaultsfor simple unlock flags or a lightweight JSON file for full milestone state - For
SwiftDataorCoreDataapps, consider storing unlock state alongside the user's data model - Sync unlock state via CloudKit if the app supports multiple devices
App Store Guidelines
- Guideline 4.5.4: Milestone-announcement pushes ("you're 1 away from a badge!") are promotional — explicit opt-in consent and an in-app opt-out are required.
- Guideline 2.3.1: Screenshots showing badges and achievements must reflect what the app actually awards — aspirational metadata is a rejection risk.
- Guideline 4.3 (Spam): Celebrations don't differentiate a thin app. If this is one of several similar apps on your account, run
app-store/originality-checkbefore submitting.
References
- templates.md — All production Swift templates
- Related:
generators/streak-tracker— Streak tracking pairs naturally with milestone celebrations - Related:
generators/share-card— Shareable achievement card rendering and social sharing - Related:
generators/variable-rewards— Variable reward schedules for deeper engagement

