Files
s21_game2_front_dan/app/(home)/main/progress/page.tsx
2025-12-23 13:02:38 +03:00

619 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect, useRef } from "react"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import {
Building2,
Trophy,
User,
Search,
Medal,
Crown,
Star,
TrendingUp,
Award,
Sparkles,
Zap,
Shield
} from "lucide-react"
import Link from "next/link"
interface Particle {
x: number
y: number
vx: number
vy: number
size: number
opacity: number
color: string
}
export default function ProgressPage() {
const [searchQuery, setSearchQuery] = useState("")
const [particles, setParticles] = useState<Particle[]>([])
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
const canvasRef = useRef<HTMLCanvasElement>(null)
// Mock data для рейтингов
const cityLeaderboard = [
{ rank: 1, name: "Moscow", points: 12450, growth: "+320" },
{ rank: 2, name: "Kazan", points: 8920, growth: "+180" },
{ rank: 3, name: "SPB", points: 7185, growth: "+150" },
{ rank: 4, name: "Novosibirsk", points: 5670, growth: "+95" },
{ rank: 5, name: "Ekaterinburg", points: 4320, growth: "+70" }
]
const playerLeaderboard = [
{ rank: 1, name: "cyber_god", points: 2840, level: 12, streak: 15 },
{ rank: 2, name: "h4ck3r_pro", points: 2650, level: 11, streak: 12 },
{ rank: 3, name: "matrix_neo", points: 2420, level: 10, streak: 8 },
{ rank: 4, name: "data_wizard", points: 2180, level: 9, streak: 6 },
{ rank: 5, name: "code_ninja", points: 1950, level: 8, streak: 5 }
]
const kazanLeaderboard = [
{ rank: 1, name: "pennytige", points: 1337, level: 5, isYou: true },
{ rank: 2, name: "kzn_hacker", points: 1280, level: 6, isYou: false },
{ rank: 3, name: "tatar_coder", points: 1150, level: 5, isYou: false },
{ rank: 4, name: "crypto_bear", points: 980, level: 4, isYou: false },
{ rank: 5, name: "byte_master", points: 875, level: 4, isYou: false }
]
const allPlayers = [
{ rank: 1, name: "cyber_god", level: 12, points: 2840, city: "Moscow", change: "up" },
{ rank: 2, name: "h4ck3r_pro", level: 11, points: 2650, city: "SPB", change: "up" },
{ rank: 3, name: "matrix_neo", level: 10, points: 2420, city: "Kazan", change: "same" },
{ rank: 4, name: "data_wizard", level: 9, points: 2180, city: "Moscow", change: "down" },
{ rank: 5, name: "code_ninja", level: 8, points: 1950, city: "Kazan", change: "up" },
{ rank: 15, name: "pennytige", level: 5, points: 1337, city: "Kazan", change: "up", isYou: true }
]
const filteredPlayers = allPlayers.filter(player =>
player.name.toLowerCase().includes(searchQuery.toLowerCase())
)
// Initialize particles
useEffect(() => {
const initParticles: Particle[] = []
const colors = ['#06b6d4', '#eab308', '#8b5cf6', '#3b82f6', '#10b981']
for (let i = 0; i < 70; i++) {
initParticles.push({
x: Math.random() * (typeof window !== 'undefined' ? window.innerWidth : 1920),
y: Math.random() * (typeof window !== 'undefined' ? window.innerHeight : 1080),
vx: (Math.random() - 0.5) * 0.7,
vy: (Math.random() - 0.5) * 0.7,
size: Math.random() * 2.5 + 1,
opacity: Math.random() * 0.5 + 0.2,
color: colors[Math.floor(Math.random() * colors.length)]
})
}
setParticles(initParticles)
}, [])
// Animate particles
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const updateCanvasSize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
updateCanvasSize()
let animationFrameId: number
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
setParticles(prevParticles => {
return prevParticles.map(particle => {
let newX = particle.x + particle.vx
let newY = particle.y + particle.vy
if (newX < 0 || newX > canvas.width) particle.vx *= -1
if (newY < 0 || newY > canvas.height) particle.vy *= -1
newX = Math.max(0, Math.min(canvas.width, newX))
newY = Math.max(0, Math.min(canvas.height, newY))
ctx.shadowBlur = 10
ctx.shadowColor = particle.color
ctx.fillStyle = `${particle.color}${Math.floor(particle.opacity * 255).toString(16).padStart(2, '0')}`
ctx.beginPath()
ctx.arc(newX, newY, particle.size, 0, Math.PI * 2)
ctx.fill()
return { ...particle, x: newX, y: newY }
})
})
animationFrameId = requestAnimationFrame(animate)
}
animate()
window.addEventListener('resize', updateCanvasSize)
return () => {
cancelAnimationFrame(animationFrameId)
window.removeEventListener('resize', updateCanvasSize)
}
}, [])
// Mouse parallax
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePos({ x: e.clientX, y: e.clientY })
}
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [])
const getRankColor = (rank: number) => {
if (rank === 1) return "text-yellow-400"
if (rank === 2) return "text-gray-300"
if (rank === 3) return "text-amber-600"
return "text-cyan-400"
}
const getRankIcon = (rank: number) => {
if (rank === 1) return <Crown className="w-5 h-5 text-yellow-400" />
if (rank === 2) return <Medal className="w-5 h-5 text-gray-300" />
if (rank === 3) return <Award className="w-5 h-5 text-amber-600" />
return null
}
const getRankBg = (rank: number) => {
if (rank === 1) return "bg-gradient-to-br from-yellow-500/20 to-orange-500/20 border-yellow-500/40"
if (rank === 2) return "bg-gradient-to-br from-gray-400/20 to-gray-500/20 border-gray-400/40"
if (rank === 3) return "bg-gradient-to-br from-amber-600/20 to-orange-700/20 border-amber-600/40"
return "bg-cyan-500/5 border-cyan-500/20"
}
return (
<div className="min-h-screen bg-[#0a0e1a] relative overflow-hidden">
{/* Animated particles canvas */}
<canvas
ref={canvasRef}
className="fixed inset-0 pointer-events-none z-0"
style={{ opacity: 0.5 }}
/>
{/* Animated background effects */}
<div
className="fixed w-[500px] h-[500px] bg-cyan-500/10 rounded-full blur-[150px] animate-pulse transition-transform duration-1000"
style={{
top: '10%',
left: '15%',
transform: `translate(${(mousePos.x - (typeof window !== 'undefined' ? window.innerWidth : 1920) / 2) * 0.03}px, ${(mousePos.y - (typeof window !== 'undefined' ? window.innerHeight : 1080) / 2) * 0.03}px)`
}}
/>
<div
className="fixed w-[500px] h-[500px] bg-yellow-500/10 rounded-full blur-[150px] animate-pulse transition-transform duration-1000"
style={{
bottom: '10%',
right: '15%',
animationDelay: '1s',
transform: `translate(${-(mousePos.x - (typeof window !== 'undefined' ? window.innerWidth : 1920) / 2) * 0.03}px, ${-(mousePos.y - (typeof window !== 'undefined' ? window.innerHeight : 1080) / 2) * 0.03}px)`
}}
/>
<div
className="fixed w-[400px] h-[400px] bg-purple-500/8 rounded-full blur-[120px] animate-pulse transition-transform duration-1000"
style={{
top: '50%',
left: '50%',
animationDelay: '0.5s',
transform: `translate(-50%, -50%) translate(${(mousePos.x - (typeof window !== 'undefined' ? window.innerWidth : 1920) / 2) * 0.02}px, ${(mousePos.y - (typeof window !== 'undefined' ? window.innerHeight : 1080) / 2) * 0.02}px)`
}}
/>
{/* Header */}
<header className="bg-[#0d1117]/80 backdrop-blur-xl border-b border-cyan-500/30 sticky top-0 z-50 shadow-lg shadow-cyan-500/5">
<nav className="container mx-auto px-6 py-4">
<div className="flex items-center justify-center">
<div className="flex items-center gap-12">
<Link href="/main">
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80 hover:scale-105 cursor-pointer">
Main
</button>
</Link>
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-400 scale-110 drop-shadow-[0_0_8px_rgba(6,182,212,0.5)] cursor-pointer">
Progress
</button>
<Link href="/main/profile">
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80 hover:scale-105 cursor-pointer">
Profile
</button>
</Link>
<Link href="/main/rules">
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80 hover:scale-105 cursor-pointer">
Rules
</button>
</Link>
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80 hover:scale-105 cursor-pointer">
Logoff
</button>
</div>
</div>
</nav>
</header>
{/* Main Content */}
<main className="container mx-auto px-6 py-8 max-w-7xl relative z-10">
{/* Page Title */}
<div className="mb-8 text-center">
<div className="inline-flex items-center gap-3 mb-2">
<Trophy className="w-8 h-8 text-yellow-400 animate-pulse" />
<h1 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-blue-400 to-purple-400 font-mono">
Leaderboards
</h1>
<Trophy className="w-8 h-8 text-yellow-400 animate-pulse" />
</div>
<p className="text-cyan-400/60 font-mono text-sm">Соревнуйся с лучшими хакерами!</p>
</div>
{/* Top Leaderboards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
{/* Топ городов */}
<Card className="bg-[#0d1117]/50 backdrop-blur-xl border-2 border-cyan-500/30 p-6 shadow-[0_0_30px_rgba(6,182,212,0.1)] hover:shadow-[0_0_50px_rgba(6,182,212,0.2)] transition-all duration-300 relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
{/* Animated background pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute inset-0" style={{
backgroundImage: `radial-gradient(circle at 2px 2px, rgba(6, 182, 212, 0.4) 1px, transparent 0)`,
backgroundSize: '25px 25px',
animation: 'moveBackground 20s linear infinite'
}} />
</div>
<div className="relative z-10">
<div className="flex items-center gap-3 mb-5">
<div className="w-12 h-12 bg-gradient-to-br from-cyan-500/20 to-blue-500/20 rounded-xl flex items-center justify-center border border-cyan-500/30">
<Building2 className="w-6 h-6 text-cyan-400" />
</div>
<div>
<h3 className="text-lg font-bold text-cyan-400 font-mono">
Топ городов
</h3>
<p className="text-xs text-cyan-400/50 font-mono">City Rankings</p>
</div>
</div>
<div className="space-y-2">
{cityLeaderboard.map((entry, index) => (
<div
key={entry.rank}
className={`flex items-center justify-between p-3 rounded-xl border transition-all group/item cursor-pointer hover:scale-[1.02] ${getRankBg(entry.rank)} hover:shadow-[0_0_20px_rgba(6,182,212,0.15)]`}
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="flex items-center gap-3 flex-1">
<div className="flex items-center gap-2 w-16">
{getRankIcon(entry.rank)}
<span className={`font-bold font-mono text-sm ${getRankColor(entry.rank)}`}>
#{entry.rank}
</span>
</div>
<span className="text-cyan-400 font-mono text-sm font-semibold group-hover/item:text-cyan-300 transition-colors">
{entry.name}
</span>
</div>
<div className="flex flex-col items-end">
<span className="text-green-400 font-mono font-bold text-sm">
{entry.points.toLocaleString()}
</span>
<span className="text-green-400/60 font-mono text-xs flex items-center gap-1">
<TrendingUp className="w-3 h-3" />
{entry.growth}
</span>
</div>
</div>
))}
</div>
</div>
</Card>
{/* Топ игроков */}
<Card className="bg-[#0d1117]/50 backdrop-blur-xl border-2 border-yellow-500/30 p-6 shadow-[0_0_30px_rgba(234,179,8,0.1)] hover:shadow-[0_0_50px_rgba(234,179,8,0.2)] transition-all duration-300 relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-yellow-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
{/* Animated background pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute inset-0" style={{
backgroundImage: `radial-gradient(circle at 2px 2px, rgba(234, 179, 8, 0.4) 1px, transparent 0)`,
backgroundSize: '25px 25px',
animation: 'moveBackground 20s linear infinite'
}} />
</div>
<div className="relative z-10">
<div className="flex items-center gap-3 mb-5">
<div className="w-12 h-12 bg-gradient-to-br from-yellow-500/20 to-orange-500/20 rounded-xl flex items-center justify-center border border-yellow-500/30">
<Trophy className="w-6 h-6 text-yellow-400 animate-pulse" />
</div>
<div>
<h3 className="text-lg font-bold text-yellow-400 font-mono">
Топ игроков
</h3>
<p className="text-xs text-yellow-400/50 font-mono">Global Rankings</p>
</div>
</div>
<div className="space-y-2">
{playerLeaderboard.map((entry, index) => (
<div
key={entry.rank}
className={`flex items-center justify-between p-3 rounded-xl border transition-all group/item cursor-pointer hover:scale-[1.02] ${getRankBg(entry.rank)} hover:shadow-[0_0_20px_rgba(234,179,8,0.15)]`}
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="flex items-center gap-3 flex-1">
<div className="flex items-center gap-2 w-16">
{getRankIcon(entry.rank)}
<span className={`font-bold font-mono text-sm ${getRankColor(entry.rank)}`}>
#{entry.rank}
</span>
</div>
<div className="flex flex-col">
<span className="text-cyan-400 font-mono text-sm font-semibold group-hover/item:text-cyan-300 transition-colors">
{entry.name}
</span>
<div className="flex items-center gap-2 text-xs">
<span className="text-cyan-400/60 font-mono">Lvl {entry.level}</span>
<span className="text-orange-400 font-mono flex items-center gap-1">
<Zap className="w-3 h-3" />
{entry.streak}
</span>
</div>
</div>
</div>
<span className="text-green-400 font-mono font-bold text-sm">
{entry.points.toLocaleString()}
</span>
</div>
))}
</div>
</div>
</Card>
{/* Топ Kazan */}
<Card className="bg-[#0d1117]/50 backdrop-blur-xl border-2 border-purple-500/30 p-6 shadow-[0_0_30px_rgba(168,85,247,0.1)] hover:shadow-[0_0_50px_rgba(168,85,247,0.2)] transition-all duration-300 relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
{/* Animated background pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute inset-0" style={{
backgroundImage: `radial-gradient(circle at 2px 2px, rgba(168, 85, 247, 0.4) 1px, transparent 0)`,
backgroundSize: '25px 25px',
animation: 'moveBackground 20s linear infinite'
}} />
</div>
<div className="relative z-10">
<div className="flex items-center gap-3 mb-5">
<div className="w-12 h-12 bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-xl flex items-center justify-center border border-purple-500/30">
<Shield className="w-6 h-6 text-purple-400" />
</div>
<div>
<h3 className="text-lg font-bold text-purple-400 font-mono">
Топ Kazan
</h3>
<p className="text-xs text-purple-400/50 font-mono">Local Rankings</p>
</div>
</div>
<div className="space-y-2">
{kazanLeaderboard.map((entry, index) => (
<div
key={entry.rank}
className={`flex items-center justify-between p-3 rounded-xl border transition-all group/item cursor-pointer hover:scale-[1.02] ${
entry.isYou
? 'bg-gradient-to-br from-cyan-500/30 to-blue-500/30 border-cyan-500/60 shadow-[0_0_25px_rgba(6,182,212,0.2)] animate-pulse-slow'
: getRankBg(entry.rank)
} hover:shadow-[0_0_20px_rgba(168,85,247,0.15)]`}
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="flex items-center gap-3 flex-1">
<div className="flex items-center gap-2 w-16">
{getRankIcon(entry.rank)}
<span className={`font-bold font-mono text-sm ${getRankColor(entry.rank)}`}>
#{entry.rank}
</span>
</div>
<div className="flex items-center gap-2">
<span className={`font-mono text-sm font-semibold transition-colors ${
entry.isYou ? 'text-cyan-300' : 'text-cyan-400 group-hover/item:text-cyan-300'
}`}>
{entry.name}
</span>
{entry.isYou && (
<span className="px-2 py-0.5 bg-cyan-500 text-black text-[10px] font-mono font-bold rounded-full">
YOU
</span>
)}
</div>
</div>
<span className="text-green-400 font-mono font-bold text-sm">
{entry.points.toLocaleString()}
</span>
</div>
))}
</div>
</div>
</Card>
</div>
{/* All Players Table */}
<Card className="bg-[#0d1117]/50 backdrop-blur-xl border-2 border-cyan-500/30 p-6 shadow-[0_0_30px_rgba(6,182,212,0.1)] hover:shadow-[0_0_50px_rgba(6,182,212,0.2)] transition-all duration-300 relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-cyan-500/10 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000" />
<div className="relative z-10">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-br from-cyan-500/20 to-blue-500/20 rounded-xl flex items-center justify-center border border-cyan-500/30">
<Sparkles className="w-6 h-6 text-cyan-400 animate-pulse" />
</div>
<div>
<h3 className="text-xl font-bold text-cyan-400 font-mono">
Все игроки
</h3>
<p className="text-xs text-cyan-400/50 font-mono">Complete Rankings</p>
</div>
</div>
<div className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-cyan-500/10 to-blue-500/10 border border-cyan-500/30 rounded-lg">
<User className="w-4 h-4 text-cyan-400" />
<span className="text-sm font-mono font-bold text-cyan-400">
{allPlayers.length} игроков
</span>
</div>
</div>
{/* Search */}
<div className="mb-6 relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-cyan-400/50 pointer-events-none" />
<Input
type="text"
placeholder="Поиск игрока..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-12 bg-[#0a0e1a]/80 backdrop-blur border-cyan-500/40 text-cyan-400 placeholder:text-cyan-400/30 font-mono hover:border-cyan-500/60 transition-all focus:border-cyan-400 focus:shadow-[0_0_20px_rgba(6,182,212,0.2)] cursor-text h-12 rounded-xl"
/>
</div>
{/* Table */}
<div className="overflow-x-auto rounded-xl border border-cyan-500/20">
<table className="w-full">
<thead>
<tr className="bg-[#0a0e1a]/80 border-b border-cyan-500/30">
<th className="text-left py-4 px-6 text-sm font-mono text-cyan-400/80 uppercase font-bold">
Ранг
</th>
<th className="text-left py-4 px-6 text-sm font-mono text-cyan-400/80 uppercase font-bold">
Игрок
</th>
<th className="text-left py-4 px-6 text-sm font-mono text-cyan-400/80 uppercase font-bold">
Уровень
</th>
<th className="text-left py-4 px-6 text-sm font-mono text-cyan-400/80 uppercase font-bold">
Очки
</th>
<th className="text-left py-4 px-6 text-sm font-mono text-cyan-400/80 uppercase font-bold">
Город
</th>
<th className="text-left py-4 px-6 text-sm font-mono text-cyan-400/80 uppercase font-bold">
Тренд
</th>
</tr>
</thead>
<tbody>
{filteredPlayers.map((player, index) => (
<tr
key={player.rank}
className={`border-b border-cyan-500/10 transition-all group/row cursor-pointer ${
player.isYou
? 'bg-cyan-500/10 hover:bg-cyan-500/15'
: 'hover:bg-cyan-500/5'
}`}
style={{ animationDelay: `${index * 0.05}s` }}
>
<td className="py-4 px-6">
<div className="flex items-center gap-2">
{getRankIcon(player.rank)}
<span className={`font-bold font-mono text-sm ${getRankColor(player.rank)}`}>
#{player.rank}
</span>
</div>
</td>
<td className="py-4 px-6">
<div className="flex items-center gap-2">
<span className={`font-mono text-sm font-semibold transition-colors ${
player.isYou ? 'text-cyan-300' : 'text-cyan-400 group-hover/row:text-cyan-300'
}`}>
{player.name}
</span>
{player.isYou && (
<span className="px-2 py-0.5 bg-cyan-500 text-black text-[10px] font-mono font-bold rounded-full">
YOU
</span>
)}
</div>
</td>
<td className="py-4 px-6">
<div className="flex items-center gap-2 px-3 py-1 bg-cyan-500/10 border border-cyan-500/30 rounded-lg w-fit">
<Star className="w-3 h-3 text-cyan-400" />
<span className="text-cyan-400 font-mono text-sm font-bold">
{player.level}
</span>
</div>
</td>
<td className="py-4 px-6">
<span className="text-green-400 font-mono font-bold text-sm">
{player.points.toLocaleString()}
</span>
</td>
<td className="py-4 px-6">
<span className="text-cyan-400/70 font-mono text-sm">
{player.city}
</span>
</td>
<td className="py-4 px-6">
<div className="flex items-center gap-1">
{player.change === 'up' && <TrendingUp className="w-4 h-4 text-green-400" />}
{player.change === 'down' && <TrendingUp className="w-4 h-4 text-red-400 rotate-180" />}
{player.change === 'same' && <span className="text-cyan-400/50 font-mono text-xs"></span>}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{filteredPlayers.length === 0 && (
<div className="text-center py-16">
<div className="w-16 h-16 bg-cyan-500/10 rounded-full flex items-center justify-center mx-auto mb-4">
<Search className="w-8 h-8 text-cyan-400/50" />
</div>
<p className="text-cyan-400/50 font-mono text-lg">
Игрок не найден
</p>
<p className="text-cyan-400/30 font-mono text-sm mt-2">
Попробуйте другой запрос
</p>
</div>
)}
</div>
</Card>
</main>
{/* Enhanced Background grid */}
<div
className="fixed inset-0 pointer-events-none -z-10 opacity-40"
style={{
backgroundImage: `
linear-gradient(rgba(6, 182, 212, 0.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(6, 182, 212, 0.08) 1px, transparent 1px)
`,
backgroundSize: '60px 60px'
}}
/>
<style jsx>{`
@keyframes moveBackground {
0% { transform: translate(0, 0); }
100% { transform: translate(25px, 25px); }
}
@keyframes pulse-slow {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.animate-pulse-slow {
animation: pulse-slow 3s ease-in-out infinite;
}
`}</style>
</div>
)
}