"use client"; import { useId, type CSSProperties } from "react"; /** * Streak visuals for the flashcard viewer. A streak is a run of consecutive * "correct" grades; milestones fire a flame burst that clears itself. */ export type StreakTier = 1 | 2 | 3; /** Milestones: 5, then every 10 (10, 20, 30, ...). */ export function isStreakMilestone(streak: number) { return streak === 5 || (streak >= 10 && streak % 10 === 0); } export function streakTier(streak: number): StreakTier { if (streak >= 20) return 3; if (streak >= 10) return 2; return 1; } /** A streak this long turns the progress bar (and counter) to flame. */ export const STREAK_FLAME_THRESHOLD = 5; const TIER_COPY: Record = { 1: "On fire", 2: "Blazing", 3: "Unstoppable", }; const TIER_FLAME_SIZE: Record = { 1: "h-16 w-16", 2: "h-24 w-24", 3: "h-36 w-36 md:h-44 md:w-44", }; const TIER_HEADING_SIZE: Record = { 1: "text-lg", 2: "text-2xl", 3: "text-4xl md:text-5xl", }; const TIER_DURATION: Record = { 1: 1600, 2: 1900, 3: 2400, }; export function streakBurstDuration(tier: StreakTier) { return TIER_DURATION[tier]; } export function FlameIcon({ className }: { className?: string }) { // useId() output contains punctuation that is unsafe inside a url(#...) // reference, so strip it down to an id the gradient fill can resolve. const id = `flame-gradient-${useId().replace(/[^a-zA-Z0-9]/g, "")}`; return ( ); } /** * The celebratory flame overlay. Rendered inside the card's relative wrapper and * never interactive, so taps still reach the card underneath. */ export function StreakBurst({ streak, tier }: { streak: number; tier: StreakTier }) { return (
{TIER_COPY[tier]} {streak} in a row
); } /** The persistent "x in a row" counter shown next to the running tally. */ export function StreakCounter({ streak }: { streak: number }) { const onFire = streak >= STREAK_FLAME_THRESHOLD; return ( {onFire && } {streak} in a row ); }