export const API_VERSION = '2026-demo-v1'; export type GroupCode = 'A' | 'B' | 'C' | 'D' | 'E'; export type ItemType = 'team' | 'match' | 'stadium' | 'sticker'; export type CollectionRarity = 'common' | 'rare' | 'epic' | 'legendary'; export type MatchStage = 'group' | 'round-of-32' | 'round-of-16' | 'quarter-final' | 'semi-final' | 'third-place' | 'final'; export type MatchStatus = 'scheduled' | 'live' | 'final'; export type BadgeTier = 'bronze' | 'silver' | 'gold' | 'platinum'; export type ChallengeKind = 'collection' | 'trivia' | 'prediction' | 'memory' | 'mixed'; export type ChallengeMetric = | 'collect_any' | 'collect_all_content_ids' | 'answer_trivia_count' | 'submit_giant_killing_prediction' | 'add_memory_count' | 'complete_starter_actions'; export type MemoryMood = 'joy' | 'nerves' | 'shock' | 'pride'; export interface Team { id: string; name: string; code: string; group: GroupCode; confederation: string; fifaRankSeed: number; primaryColor: string; qualifiedStatus: 'host' | 'qualified' | 'demo'; } export interface Stadium { id: string; name: string; city: string; country: 'Canada' | 'Mexico' | 'United States'; capacity: number; imageHint: string; } export interface Match { id: string; stage: MatchStage; group?: GroupCode; kickoffISO: string; stadiumId: string; homeTeamId: string; awayTeamId: string; underdogTeamId?: string; status: MatchStatus; result?: { homeGoals: number; awayGoals: number; winnerTeamId?: string; }; } export interface TriviaOption { id: string; label: string; } export interface TriviaQuestion { id: string; day: string; category: 'history' | 'stadiums' | 'teams' | 'rules' | 'culture'; question: string; options: TriviaOption[]; correctOptionId: string; explanation: string; points: number; } export interface CollectionItem { id: string; itemType: ItemType; contentId: string; rarity: CollectionRarity; title: string; description: string; imageHint: string; setId: string; } export interface BadgeDefinition { id: string; name: string; description: string; icon: string; tier: BadgeTier; prestige: number; } export interface ChallengeDefinition { id: string; title: string; description: string; kind: ChallengeKind; metric: ChallengeMetric; target: number; points: number; badgeId?: string; itemType?: ItemType; contentIds?: string[]; } export interface UserProfile { id: string; displayName: string; country: string; createdAt: string; points: number; level: number; streakDays: number; lastActiveDate: string; } export interface UserCollectionEntry { id: string; itemType: ItemType; contentId: string; collectedAt: string; pointsAwarded: number; source: 'manual' | 'demo-script' | 'system'; } export interface TriviaAnswer { questionId: string; selectedOptionId: string; correct: boolean; answeredAt: string; pointsAwarded: number; } export interface Prediction { id: string; userId: string; matchId: string; predictedWinnerTeamId: string; confidence: 1 | 2 | 3 | 4 | 5; giantKilling: boolean; status: 'submitted' | 'settled'; correct?: boolean; createdAt: string; updatedAt: string; pointsAwarded: number; } export interface PassportMemory { id: string; title: string; note: string; matchId?: string; mood: MemoryMood; createdAt: string; pointsAwarded: number; } export interface BadgeAward { badgeId: string; awardedAt: string; reason: string; } export interface ChallengeProgress { challengeId: string; current: number; target: number; complete: boolean; completedAt?: string; } export interface AchievementEvent { id: string; type: 'points' | 'badge' | 'challenge' | 'collection' | 'trivia' | 'prediction' | 'memory'; title: string; description: string; points: number; createdAt: string; } export interface LeaderboardSeedEntry { userId: string; displayName: string; country: string; points: number; badges: number; completedChallenges: number; avatarEmoji: string; } export interface LeaderboardEntry extends LeaderboardSeedEntry { rank: number; isCurrentUser?: boolean; } export interface PassportStats { totalCollections: number; teamsCollected: number; stadiumsCollected: number; matchesLogged: number; stickersCollected: number; correctTriviaAnswers: number; predictionsSubmitted: number; giantKillingsPredicted: number; memoriesCreated: number; completedChallenges: number; rank: number; } export interface PassportState { user: UserProfile; collections: UserCollectionEntry[]; triviaAnswers: TriviaAnswer[]; predictions: Prediction[]; memories: PassportMemory[]; badges: BadgeAward[]; challengeProgress: ChallengeProgress[]; achievements: AchievementEvent[]; leaderboard: LeaderboardEntry[]; stats: PassportStats; } export interface ContentResponse { version: string; teams: Team[]; stadiums: Stadium[]; matches: Match[]; triviaQuestions: TriviaQuestion[]; collectionItems: CollectionItem[]; badges: BadgeDefinition[]; challenges: ChallengeDefinition[]; } export interface ApiErrorResponse { error: { code: string; message: string; details?: unknown; }; } export interface CreateUserRequest { displayName: string; country?: string; } export interface CreateUserResponse { user: UserProfile; passport: PassportState; } export interface CollectItemRequest { userId: string; itemType: ItemType; contentId: string; source?: 'manual' | 'demo-script' | 'system'; } export interface AnswerTriviaRequest { userId: string; selectedOptionId: string; } export interface SubmitPredictionRequest { userId: string; matchId: string; predictedWinnerTeamId: string; confidence: 1 | 2 | 3 | 4 | 5; } export interface AddMemoryRequest { userId: string; title: string; note: string; matchId?: string; mood: MemoryMood; } export interface ActionResponse { passport: PassportState; achievementEvents: AchievementEvent[]; message: string; } export interface LeaderboardResponse { leaderboard: LeaderboardEntry[]; } export const seedTeams: Team[] = [ { id: 'mexico', name: 'Mexico', code: 'MEX', group: 'A', confederation: 'CONCACAF', fifaRankSeed: 14, primaryColor: '#006847', qualifiedStatus: 'host' }, { id: 'canada', name: 'Canada', code: 'CAN', group: 'A', confederation: 'CONCACAF', fifaRankSeed: 48, primaryColor: '#d80621', qualifiedStatus: 'host' }, { id: 'japan', name: 'Japan', code: 'JPN', group: 'A', confederation: 'AFC', fifaRankSeed: 18, primaryColor: '#003f9f', qualifiedStatus: 'demo' }, { id: 'morocco', name: 'Morocco', code: 'MAR', group: 'A', confederation: 'CAF', fifaRankSeed: 13, primaryColor: '#c1272d', qualifiedStatus: 'demo' }, { id: 'england', name: 'England', code: 'ENG', group: 'B', confederation: 'UEFA', fifaRankSeed: 5, primaryColor: '#102b5c', qualifiedStatus: 'demo' }, { id: 'usa', name: 'USA', code: 'USA', group: 'B', confederation: 'CONCACAF', fifaRankSeed: 11, primaryColor: '#1f4aa8', qualifiedStatus: 'host' }, { id: 'iran', name: 'Iran', code: 'IRN', group: 'B', confederation: 'AFC', fifaRankSeed: 21, primaryColor: '#239f40', qualifiedStatus: 'demo' }, { id: 'wales', name: 'Wales', code: 'WAL', group: 'B', confederation: 'UEFA', fifaRankSeed: 29, primaryColor: '#c8102e', qualifiedStatus: 'demo' }, { id: 'argentina', name: 'Argentina', code: 'ARG', group: 'C', confederation: 'CONMEBOL', fifaRankSeed: 1, primaryColor: '#75aadb', qualifiedStatus: 'demo' }, { id: 'senegal', name: 'Senegal', code: 'SEN', group: 'C', confederation: 'CAF', fifaRankSeed: 17, primaryColor: '#00853f', qualifiedStatus: 'demo' }, { id: 'scotland', name: 'Scotland', code: 'SCO', group: 'C', confederation: 'UEFA', fifaRankSeed: 36, primaryColor: '#005eb8', qualifiedStatus: 'demo' }, { id: 'saudi-arabia', name: 'Saudi Arabia', code: 'KSA', group: 'C', confederation: 'AFC', fifaRankSeed: 54, primaryColor: '#006c35', qualifiedStatus: 'demo' }, { id: 'france', name: 'France', code: 'FRA', group: 'D', confederation: 'UEFA', fifaRankSeed: 2, primaryColor: '#1d428a', qualifiedStatus: 'demo' }, { id: 'brazil', name: 'Brazil', code: 'BRA', group: 'D', confederation: 'CONMEBOL', fifaRankSeed: 4, primaryColor: '#ffdf00', qualifiedStatus: 'demo' }, { id: 'australia', name: 'Australia', code: 'AUS', group: 'D', confederation: 'AFC', fifaRankSeed: 27, primaryColor: '#ffcd00', qualifiedStatus: 'demo' }, { id: 'ghana', name: 'Ghana', code: 'GHA', group: 'D', confederation: 'CAF', fifaRankSeed: 60, primaryColor: '#fcd116', qualifiedStatus: 'demo' }, { id: 'spain', name: 'Spain', code: 'ESP', group: 'E', confederation: 'UEFA', fifaRankSeed: 8, primaryColor: '#c60b1e', qualifiedStatus: 'demo' }, { id: 'germany', name: 'Germany', code: 'GER', group: 'E', confederation: 'UEFA', fifaRankSeed: 16, primaryColor: '#000000', qualifiedStatus: 'demo' }, { id: 'south-korea', name: 'South Korea', code: 'KOR', group: 'E', confederation: 'AFC', fifaRankSeed: 23, primaryColor: '#c60c30', qualifiedStatus: 'demo' }, { id: 'nigeria', name: 'Nigeria', code: 'NGA', group: 'E', confederation: 'CAF', fifaRankSeed: 31, primaryColor: '#008751', qualifiedStatus: 'demo' } ]; export const seedStadiums: Stadium[] = [ { id: 'metlife-stadium', name: 'MetLife Stadium', city: 'East Rutherford, New Jersey', country: 'United States', capacity: 82500, imageHint: 'New York/New Jersey skyline stamp' }, { id: 'sofi-stadium', name: 'SoFi Stadium', city: 'Inglewood, California', country: 'United States', capacity: 70240, imageHint: 'California roof halo stamp' }, { id: 'att-stadium', name: 'AT&T Stadium', city: 'Arlington, Texas', country: 'United States', capacity: 80000, imageHint: 'Texas star night-game stamp' }, { id: 'mercedes-benz-stadium', name: 'Mercedes-Benz Stadium', city: 'Atlanta, Georgia', country: 'United States', capacity: 71000, imageHint: 'Atlanta aperture roof stamp' }, { id: 'nrg-stadium', name: 'NRG Stadium', city: 'Houston, Texas', country: 'United States', capacity: 72220, imageHint: 'Houston energy beam stamp' }, { id: 'lincoln-financial-field', name: 'Lincoln Financial Field', city: 'Philadelphia, Pennsylvania', country: 'United States', capacity: 67594, imageHint: 'Philadelphia liberty bell stamp' }, { id: 'lumen-field', name: 'Lumen Field', city: 'Seattle, Washington', country: 'United States', capacity: 68740, imageHint: 'Seattle soundwave stamp' }, { id: 'levis-stadium', name: "Levi's Stadium", city: 'Santa Clara, California', country: 'United States', capacity: 68500, imageHint: 'Bay Area gold rush stamp' }, { id: 'hard-rock-stadium', name: 'Hard Rock Stadium', city: 'Miami Gardens, Florida', country: 'United States', capacity: 65326, imageHint: 'Miami palm floodlights stamp' }, { id: 'gillette-stadium', name: 'Gillette Stadium', city: 'Foxborough, Massachusetts', country: 'United States', capacity: 65878, imageHint: 'Boston colonial flag stamp' }, { id: 'arrowhead-stadium', name: 'Arrowhead Stadium', city: 'Kansas City, Missouri', country: 'United States', capacity: 76416, imageHint: 'Kansas City noise meter stamp' }, { id: 'bc-place', name: 'BC Place', city: 'Vancouver, British Columbia', country: 'Canada', capacity: 54500, imageHint: 'Vancouver mountain dome stamp' }, { id: 'bmo-field', name: 'BMO Field', city: 'Toronto, Ontario', country: 'Canada', capacity: 45000, imageHint: 'Toronto lakefront stamp' }, { id: 'estadio-azteca', name: 'Estadio Azteca', city: 'Mexico City', country: 'Mexico', capacity: 87523, imageHint: 'Azteca cathedral stamp' }, { id: 'estadio-akron', name: 'Estadio Akron', city: 'Guadalajara', country: 'Mexico', capacity: 49850, imageHint: 'Guadalajara volcano stamp' }, { id: 'estadio-bbva', name: 'Estadio BBVA', city: 'Monterrey', country: 'Mexico', capacity: 53500, imageHint: 'Monterrey mountain stamp' } ]; export const seedMatches: Match[] = [ { id: 'match-mexico-canada', stage: 'group', group: 'A', kickoffISO: '2026-06-11T19:00:00.000Z', stadiumId: 'estadio-azteca', homeTeamId: 'mexico', awayTeamId: 'canada', underdogTeamId: 'canada', status: 'scheduled' }, { id: 'match-japan-morocco', stage: 'group', group: 'A', kickoffISO: '2026-06-12T22:00:00.000Z', stadiumId: 'bc-place', homeTeamId: 'japan', awayTeamId: 'morocco', underdogTeamId: 'japan', status: 'scheduled' }, { id: 'match-england-usa', stage: 'group', group: 'B', kickoffISO: '2026-06-13T01:00:00.000Z', stadiumId: 'metlife-stadium', homeTeamId: 'england', awayTeamId: 'usa', underdogTeamId: 'usa', status: 'scheduled' }, { id: 'match-england-iran', stage: 'group', group: 'B', kickoffISO: '2026-06-17T20:00:00.000Z', stadiumId: 'nrg-stadium', homeTeamId: 'england', awayTeamId: 'iran', underdogTeamId: 'iran', status: 'scheduled' }, { id: 'match-wales-england', stage: 'group', group: 'B', kickoffISO: '2026-06-22T19:00:00.000Z', stadiumId: 'lincoln-financial-field', homeTeamId: 'wales', awayTeamId: 'england', underdogTeamId: 'wales', status: 'scheduled' }, { id: 'match-argentina-senegal', stage: 'group', group: 'C', kickoffISO: '2026-06-14T20:00:00.000Z', stadiumId: 'att-stadium', homeTeamId: 'argentina', awayTeamId: 'senegal', underdogTeamId: 'senegal', status: 'scheduled' }, { id: 'match-brazil-ghana', stage: 'group', group: 'D', kickoffISO: '2026-06-15T23:00:00.000Z', stadiumId: 'hard-rock-stadium', homeTeamId: 'brazil', awayTeamId: 'ghana', underdogTeamId: 'ghana', status: 'scheduled' }, { id: 'match-france-australia', stage: 'group', group: 'D', kickoffISO: '2026-06-16T23:00:00.000Z', stadiumId: 'sofi-stadium', homeTeamId: 'france', awayTeamId: 'australia', underdogTeamId: 'australia', status: 'scheduled' }, { id: 'match-spain-germany', stage: 'group', group: 'E', kickoffISO: '2026-06-18T20:00:00.000Z', stadiumId: 'mercedes-benz-stadium', homeTeamId: 'spain', awayTeamId: 'germany', underdogTeamId: 'germany', status: 'scheduled' }, { id: 'match-south-korea-nigeria', stage: 'group', group: 'E', kickoffISO: '2026-06-19T23:00:00.000Z', stadiumId: 'lumen-field', homeTeamId: 'south-korea', awayTeamId: 'nigeria', underdogTeamId: 'nigeria', status: 'scheduled' } ]; export const seedTriviaQuestions: TriviaQuestion[] = [ { id: 'trivia-stadium-eng-usa', day: '2026-06-13', category: 'stadiums', question: 'Which stadium hosts the demo fixture England vs USA?', options: [ { id: 'metlife', label: 'MetLife Stadium' }, { id: 'azteca', label: 'Estadio Azteca' }, { id: 'bc-place', label: 'BC Place' }, { id: 'sofi', label: 'SoFi Stadium' } ], correctOptionId: 'metlife', explanation: 'The demo fixture England vs USA is seeded at MetLife Stadium in New Jersey.', points: 30 }, { id: 'trivia-hosts-2026', day: '2026-06-14', category: 'culture', question: 'Which three countries are hosting the 2026 FIFA World Cup?', options: [ { id: 'us-can-mex', label: 'United States, Canada, and Mexico' }, { id: 'us-bra-arg', label: 'United States, Brazil, and Argentina' }, { id: 'mex-can-eng', label: 'Mexico, Canada, and England' }, { id: 'spa-por-mor', label: 'Spain, Portugal, and Morocco' } ], correctOptionId: 'us-can-mex', explanation: 'The 2026 tournament is jointly hosted by the United States, Canada, and Mexico.', points: 25 }, { id: 'trivia-group-win-points', day: '2026-06-15', category: 'rules', question: 'How many points does a team receive for a group-stage win?', options: [ { id: 'one', label: '1 point' }, { id: 'two', label: '2 points' }, { id: 'three', label: '3 points' }, { id: 'four', label: '4 points' } ], correctOptionId: 'three', explanation: 'A group-stage win is worth 3 points; a draw is worth 1 point.', points: 20 }, { id: 'trivia-azteca-history', day: '2026-06-16', category: 'history', question: 'Which legendary Mexico City venue appears in the stadium collection?', options: [ { id: 'azteca', label: 'Estadio Azteca' }, { id: 'maracana', label: 'Maracanã' }, { id: 'wembley', label: 'Wembley Stadium' }, { id: 'san-siro', label: 'San Siro' } ], correctOptionId: 'azteca', explanation: 'Estadio Azteca is one of the iconic venues represented in the passport.', points: 20 }, { id: 'trivia-underdog', day: '2026-06-17', category: 'teams', question: 'In the demo prediction model, what is a “giant killing”?', options: [ { id: 'favorite-win', label: 'The top seed wins comfortably' }, { id: 'underdog-win', label: 'An underdog is predicted to beat a stronger opponent' }, { id: 'draw', label: 'Both teams draw 0-0' }, { id: 'penalty', label: 'A goalkeeper saves a penalty' } ], correctOptionId: 'underdog-win', explanation: 'A giant killing is the classic upset: the underdog beats the favourite.', points: 25 }, { id: 'trivia-sticker-book', day: '2026-06-18', category: 'culture', question: 'What does the virtual sticker collection recreate?', options: [ { id: 'fantasy-lineup', label: 'A fantasy transfer market' }, { id: 'sticker-album', label: 'The ritual of filling a tournament sticker album' }, { id: 'stadium-map', label: 'A parking map' }, { id: 'ticket-wallet', label: 'Only match ticket storage' } ], correctOptionId: 'sticker-album', explanation: 'The sticker collection mirrors the nostalgia of filling a World Cup sticker album.', points: 20 } ]; const teamName = (teamId: string): string => seedTeams.find((team) => team.id === teamId)?.name ?? teamId; const stadiumName = (stadiumId: string): string => seedStadiums.find((stadium) => stadium.id === stadiumId)?.name ?? stadiumId; const teamCollectionItems: CollectionItem[] = seedTeams.map((team) => ({ id: `collection-team-${team.id}`, itemType: 'team', contentId: team.id, rarity: team.qualifiedStatus === 'host' ? 'rare' : team.fifaRankSeed <= 5 ? 'epic' : 'common', title: team.name, description: `Stamp ${team.name} into your Group ${team.group} collection.`, imageHint: `${team.code} crest-inspired digital stamp`, setId: `group-${team.group.toLowerCase()}` })); const stadiumCollectionItems: CollectionItem[] = seedStadiums.map((stadium, index) => ({ id: `collection-stadium-${stadium.id}`, itemType: 'stadium', contentId: stadium.id, rarity: index % 5 === 0 ? 'epic' : index % 3 === 0 ? 'rare' : 'common', title: stadium.name, description: `Collect the ${stadium.city} venue stamp.`, imageHint: stadium.imageHint, setId: 'stadium-tour' })); const matchCollectionItems: CollectionItem[] = seedMatches.map((match) => ({ id: `collection-match-${match.id}`, itemType: 'match', contentId: match.id, rarity: match.underdogTeamId ? 'rare' : 'common', title: `${teamName(match.homeTeamId)} vs ${teamName(match.awayTeamId)}`, description: `Log the Group ${match.group ?? 'knockout'} match at ${stadiumName(match.stadiumId)}.`, imageHint: `Matchday card for ${teamName(match.homeTeamId)} vs ${teamName(match.awayTeamId)}`, setId: 'matchdays' })); const stickerCollectionItems: CollectionItem[] = [ { id: 'collection-sticker-mascot-maple', itemType: 'sticker', contentId: 'sticker-mascot-maple', rarity: 'common', title: 'Maple Mascot', description: 'A cheerful Canada-host sticker for the virtual album.', imageHint: 'Mascot waving a maple-leaf scarf', setId: 'virtual-stickers' }, { id: 'collection-sticker-azteca-night', itemType: 'sticker', contentId: 'sticker-azteca-night', rarity: 'epic', title: 'Azteca Under Lights', description: 'A night-match sticker inspired by Mexico City.', imageHint: 'Floodlights over Estadio Azteca', setId: 'virtual-stickers' }, { id: 'collection-sticker-star-spangled-stand', itemType: 'sticker', contentId: 'sticker-star-spangled-stand', rarity: 'rare', title: 'Star-Spangled Stand', description: 'A USA host-city crowd sticker.', imageHint: 'Red, white, and blue supporters section', setId: 'virtual-stickers' }, { id: 'collection-sticker-golden-boot', itemType: 'sticker', contentId: 'sticker-golden-boot', rarity: 'legendary', title: 'Golden Boot Chase', description: 'A premium sticker for fans tracking the tournament scorers.', imageHint: 'Golden boot on a confetti pitch', setId: 'virtual-stickers' }, { id: 'collection-sticker-underdog-roar', itemType: 'sticker', contentId: 'sticker-underdog-roar', rarity: 'rare', title: 'Underdog Roar', description: 'A sticker celebrating giant-killing predictions.', imageHint: 'Underdog fans erupting behind the goal', setId: 'virtual-stickers' }, { id: 'collection-sticker-passport-control', itemType: 'sticker', contentId: 'sticker-passport-control', rarity: 'common', title: 'Passport Control', description: 'The official-looking stamp that starts every collection page.', imageHint: 'Ink stamp with football seams', setId: 'virtual-stickers' } ]; export const seedCollectionItems: CollectionItem[] = [ ...teamCollectionItems, ...stadiumCollectionItems, ...matchCollectionItems, ...stickerCollectionItems ]; export const groupBTeamIds = seedTeams.filter((team) => team.group === 'B').map((team) => team.id); export const englandMatchIds = seedMatches .filter((match) => match.homeTeamId === 'england' || match.awayTeamId === 'england') .map((match) => match.id); export const stadiumIds = seedStadiums.map((stadium) => stadium.id); export const seedBadgeDefinitions: BadgeDefinition[] = [ { id: 'badge-kickoff-passport', name: 'Kick-off Passport', description: 'Started the journey by completing the first cross-feature actions.', icon: '🛂', tier: 'bronze', prestige: 10 }, { id: 'badge-group-b-complete', name: 'Group B Complete', description: 'Collected every seeded Group B team.', icon: '✅', tier: 'silver', prestige: 25 }, { id: 'badge-stadium-tour', name: 'Stadium Tour', description: 'Collected every 2026 host stadium in the demo seed.', icon: '🏟️', tier: 'gold', prestige: 50 }, { id: 'badge-england-superfan', name: 'England Superfan', description: 'Logged every seeded England match.', icon: '🦁', tier: 'silver', prestige: 30 }, { id: 'badge-trivia-streak', name: 'Trivia Streak', description: 'Answered three daily World Cup trivia questions.', icon: '🧠', tier: 'silver', prestige: 30 }, { id: 'badge-giant-killer', name: 'Giant Killer', description: 'Predicted an underdog upset.', icon: '⚡', tier: 'gold', prestige: 45 }, { id: 'badge-sticker-rookie', name: 'Sticker Rookie', description: 'Collected five virtual stickers.', icon: '📒', tier: 'bronze', prestige: 15 }, { id: 'badge-memory-keeper', name: 'Memory Keeper', description: 'Saved a personal tournament memory.', icon: '📸', tier: 'bronze', prestige: 15 } ]; export const seedChallengeDefinitions: ChallengeDefinition[] = [ { id: 'challenge-kickoff-passport', title: 'Kick-off passport', description: 'Collect one item, answer trivia, submit one prediction, and save one memory.', kind: 'mixed', metric: 'complete_starter_actions', target: 4, points: 75, badgeId: 'badge-kickoff-passport' }, { id: 'challenge-group-b-complete', title: 'Complete every Group B team', description: 'Collect England, USA, Iran, and Wales to finish the seeded Group B page.', kind: 'collection', metric: 'collect_all_content_ids', itemType: 'team', contentIds: groupBTeamIds, target: groupBTeamIds.length, points: 120, badgeId: 'badge-group-b-complete' }, { id: 'challenge-stadium-tour', title: 'Collect all stadiums', description: 'Collect every stadium stamp from the 2026 host-city tour.', kind: 'collection', metric: 'collect_all_content_ids', itemType: 'stadium', contentIds: stadiumIds, target: stadiumIds.length, points: 250, badgeId: 'badge-stadium-tour' }, { id: 'challenge-england-superfan', title: 'Watch all England matches', description: 'Log every seeded England group match in your passport.', kind: 'collection', metric: 'collect_all_content_ids', itemType: 'match', contentIds: englandMatchIds, target: englandMatchIds.length, points: 150, badgeId: 'badge-england-superfan' }, { id: 'challenge-trivia-streak', title: 'Answer daily World Cup trivia', description: 'Answer three trivia questions to build a daily habit.', kind: 'trivia', metric: 'answer_trivia_count', target: 3, points: 90, badgeId: 'badge-trivia-streak' }, { id: 'challenge-giant-killing', title: 'Predict a giant killing', description: 'Back an underdog to win one seeded fixture.', kind: 'prediction', metric: 'submit_giant_killing_prediction', target: 1, points: 80, badgeId: 'badge-giant-killer' }, { id: 'challenge-sticker-rookie', title: 'Build a virtual sticker collection', description: 'Collect five virtual stickers for your album.', kind: 'collection', metric: 'collect_any', itemType: 'sticker', target: 5, points: 70, badgeId: 'badge-sticker-rookie' }, { id: 'challenge-memory-keeper', title: 'Save a tournament memory', description: 'Write down one moment you want to remember.', kind: 'memory', metric: 'add_memory_count', target: 1, points: 50, badgeId: 'badge-memory-keeper' } ]; export const seedLeaderboardRivals: LeaderboardSeedEntry[] = [ { userId: 'rival-samurai-blue', displayName: 'Samurai Blue Crew', country: 'Japan', points: 430, badges: 7, completedChallenges: 5, avatarEmoji: '🇯🇵' }, { userId: 'rival-three-lions', displayName: 'Three Lions Away End', country: 'England', points: 315, badges: 5, completedChallenges: 4, avatarEmoji: '🏴' }, { userId: 'rival-canuck-wall', displayName: 'Canuck Wall', country: 'Canada', points: 220, badges: 4, completedChallenges: 3, avatarEmoji: '🇨🇦' }, { userId: 'rival-azteca-noise', displayName: 'Azteca Noise', country: 'Mexico', points: 155, badges: 3, completedChallenges: 2, avatarEmoji: '🇲🇽' }, { userId: 'rival-underdog-union', displayName: 'Underdog Union', country: 'Global', points: 95, badges: 2, completedChallenges: 1, avatarEmoji: '⚡' }, { userId: 'rival-sticker-kid', displayName: 'Sticker Kid', country: 'Global', points: 45, badges: 1, completedChallenges: 0, avatarEmoji: '📒' } ]; export function deriveLevel(points: number): number { if (points < 100) return 1; if (points < 250) return 2; if (points < 500) return 3; if (points < 900) return 4; return 5 + Math.floor((points - 900) / 400); } export function getCollectionKey(itemType: ItemType, contentId: string): string { return `${itemType}:${contentId}`; } export function getChallengeDefinition(challengeId: string): ChallengeDefinition | undefined { return seedChallengeDefinitions.find((challenge) => challenge.id === challengeId); } export function getBadgeDefinition(badgeId: string): BadgeDefinition | undefined { return seedBadgeDefinitions.find((badge) => badge.id === badgeId); }