All files / web/src/arcade-games/know-your-world/utils celebration.ts

77.77% Statements 70/90
100% Branches 0/0
0% Functions 0/1
77.77% Lines 70/90

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 911x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                         1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
/**
 * Celebration Classification Utility
 *
 * Determines the type of celebration based on how the user found the region.
 * Uses search metrics from the hot/cold feedback system to classify:
 * - Lightning: Fast and direct find
 * - Standard: Normal discovery
 * - Hard-earned: Extensive searching, shows perseverance
 */
 
import type { SearchMetrics } from '../hooks/useHotColdFeedback'
import type { CelebrationType } from '../Provider'
 
// Thresholds for classification
const LIGHTNING_TIME_MS = 3000 // Under 3 seconds
const LIGHTNING_EFFICIENCY = 0.7 // Very direct path
 
const HARD_EARNED_TIME_MS = 20000 // Over 20 seconds
const HARD_EARNED_EFFICIENCY = 0.3 // Wandered a lot
const HARD_EARNED_REVERSALS = 10 // Many direction changes
const HARD_EARNED_NEAR_MISSES = 2 // Got close multiple times
 
/**
 * Classify the celebration type based on search metrics.
 *
 * @param metrics - Search metrics from useHotColdFeedback
 * @returns The celebration type: 'lightning', 'standard', or 'hard-earned'
 */
export function classifyCelebration(metrics: SearchMetrics): CelebrationType {
  // Lightning: Fast and direct
  // Kid knew exactly where to look - reward the speed!
  if (metrics.timeToFind < LIGHTNING_TIME_MS && metrics.searchEfficiency > LIGHTNING_EFFICIENCY) {
    return 'lightning'
  }

  // Hard-earned: Any of these indicate real effort
  // Kid really worked for it - acknowledge the perseverance!
  if (
    metrics.timeToFind > HARD_EARNED_TIME_MS ||
    metrics.searchEfficiency < HARD_EARNED_EFFICIENCY ||
    metrics.directionReversals > HARD_EARNED_REVERSALS ||
    metrics.nearMissCount > HARD_EARNED_NEAR_MISSES
  ) {
    return 'hard-earned'
  }

  // Standard: Normal discovery
  return 'standard'
}
 
// Celebration timing configuration
export const CELEBRATION_TIMING = {
  lightning: {
    flashDuration: 400,
    confettiDuration: 600,
    soundDuration: 200,
    totalDuration: 600,
  },
  standard: {
    flashDuration: 600,
    confettiDuration: 1000,
    soundDuration: 400,
    totalDuration: 1000,
  },
  'hard-earned': {
    flashDuration: 800,
    confettiDuration: 1500,
    soundDuration: 600,
    totalDuration: 1500,
  },
} as const
 
// Confetti configuration per celebration type
export const CONFETTI_CONFIG = {
  lightning: {
    count: 12,
    spread: 60,
    colors: ['#fbbf24', '#fcd34d', '#fef3c7'], // Gold sparkles
  },
  standard: {
    count: 20,
    spread: 90,
    colors: ['#fbbf24', '#22c55e', '#3b82f6', '#f472b6'], // Colorful mix
  },
  'hard-earned': {
    count: 35,
    spread: 120,
    colors: ['#fbbf24', '#22c55e', '#8b5cf6', '#ec4899', '#f97316'], // Big party!
  },
} as const