зеркало из
https://github.com/kodackx/disinformation-quest.git
synced 2025-10-29 12:46:03 +02:00
Add strategy animations for clarity
Implemented animations for each strategy in the game to enhance understanding of disinformation tactics. This addition aims to provide clearer educational context for players regarding the mechanics of each strategem. [skip gpt_engineer]
Этот коммит содержится в:
родитель
f5939d07d5
Коммит
93bf8bf364
@ -1,5 +1,10 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import React from 'react';
|
||||
import { NetworkAnimation } from './animations/NetworkAnimation';
|
||||
import { MemeAnimation } from './animations/MemeAnimation';
|
||||
import { NewsAnimation } from './animations/NewsAnimation';
|
||||
import { CommunityAnimation } from './animations/CommunityAnimation';
|
||||
import { ExpertAnimation } from './animations/ExpertAnimation';
|
||||
import { PodcastAnimation } from './animations/PodcastAnimation';
|
||||
import { StrategyAnimation as StrategyAnimationType } from './types';
|
||||
|
||||
interface StrategyAnimationProps {
|
||||
@ -7,325 +12,29 @@ interface StrategyAnimationProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface Node {
|
||||
id: number;
|
||||
baseX: number;
|
||||
baseY: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface MemeSymbol {
|
||||
id: number;
|
||||
symbol: string;
|
||||
x: number;
|
||||
y: number;
|
||||
rotation: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export const StrategyAnimation: React.FC<StrategyAnimationProps> = ({ animation, className = '' }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { type } = animation;
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const container = containerRef.current;
|
||||
const { type, config = {} } = animation;
|
||||
const {
|
||||
particleCount = 20,
|
||||
speed = 2,
|
||||
spread = 100,
|
||||
color = '#FFD700'
|
||||
} = config;
|
||||
|
||||
// Clear any existing particles
|
||||
container.innerHTML = '';
|
||||
|
||||
switch (type) {
|
||||
case 'network':
|
||||
// We'll handle network differently now - moved to JSX
|
||||
break;
|
||||
|
||||
case 'meme':
|
||||
// Meme case handled separately
|
||||
break;
|
||||
|
||||
case 'news':
|
||||
// Create scrolling headlines
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const headline = document.createElement('div');
|
||||
headline.className = 'absolute left-0 whitespace-nowrap animate-scroll';
|
||||
headline.textContent = 'BREAKING NEWS • MATHEMATICAL TRUTH QUESTIONED •';
|
||||
headline.style.top = `${i * 20}%`;
|
||||
headline.style.animationDelay = `${i * 0.5}s`;
|
||||
container.appendChild(headline);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'community':
|
||||
// Create gathering dots that form groups
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const person = document.createElement('div');
|
||||
person.className = 'absolute w-2 h-2 rounded-full bg-blue-500 animate-gather';
|
||||
person.style.left = `${Math.random() * 100}%`;
|
||||
person.style.top = `${Math.random() * 100}%`;
|
||||
person.style.animationDelay = `${Math.random() * 2}s`;
|
||||
container.appendChild(person);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'expert':
|
||||
// Create floating mathematical symbols
|
||||
const symbols = ['∑', '∫', 'π', '∞', '≠', '±'];
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const symbol = document.createElement('div');
|
||||
symbol.className = 'absolute text-xl font-bold text-yellow-500 animate-float';
|
||||
symbol.textContent = symbols[Math.floor(Math.random() * symbols.length)];
|
||||
symbol.style.left = `${Math.random() * 100}%`;
|
||||
symbol.style.animationDelay = `${Math.random() * 2}s`;
|
||||
container.appendChild(symbol);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'research':
|
||||
// Create scrolling paper effect
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const paper = document.createElement('div');
|
||||
paper.className = 'absolute w-16 h-20 bg-white/20 rounded animate-float';
|
||||
paper.style.left = `${20 + i * 30}%`;
|
||||
paper.style.animationDelay = `${i * 0.5}s`;
|
||||
container.appendChild(paper);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'podcast':
|
||||
// Create audio wave effect
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const wave = document.createElement('div');
|
||||
wave.className = 'absolute bottom-1/2 w-1 bg-green-500 animate-wave';
|
||||
wave.style.left = `${10 + i * 10}%`;
|
||||
wave.style.animationDelay = `${i * 0.1}s`;
|
||||
wave.style.height = '20%';
|
||||
container.appendChild(wave);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'event':
|
||||
// Create gathering effect with people icons
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const person = document.createElement('div');
|
||||
person.className = 'absolute text-sm animate-gather';
|
||||
person.textContent = '👤';
|
||||
person.style.left = `${Math.random() * 100}%`;
|
||||
person.style.top = `${Math.random() * 100}%`;
|
||||
person.style.animationDelay = `${Math.random() * 2}s`;
|
||||
container.appendChild(person);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'platform':
|
||||
// Create platform interface elements
|
||||
const elements = ['📱', '💻', '🖥️', '📲'];
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'absolute text-2xl animate-float';
|
||||
element.textContent = elements[i];
|
||||
element.style.left = `${25 * i}%`;
|
||||
element.style.animationDelay = `${i * 0.5}s`;
|
||||
container.appendChild(element);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'freedom':
|
||||
// Create rising particles effect
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'absolute w-1 h-1 rounded-full bg-yellow-500 animate-rise';
|
||||
particle.style.left = `${Math.random() * 100}%`;
|
||||
particle.style.animationDelay = `${Math.random() * 2}s`;
|
||||
container.appendChild(particle);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}, [animation]);
|
||||
|
||||
if (animation.type === 'network') {
|
||||
const nodes: Node[] = Array.from({ length: 6 }, (_, i) => {
|
||||
const row = Math.floor(i / 2);
|
||||
const col = i % 2;
|
||||
const baseX = 30 + col * 40;
|
||||
const baseY = 25 + row * 25;
|
||||
return {
|
||||
id: i,
|
||||
baseX,
|
||||
baseY,
|
||||
x: baseX,
|
||||
y: baseY,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
<svg className="absolute inset-0 w-full h-full pointer-events-none">
|
||||
{nodes.map((node1) =>
|
||||
nodes
|
||||
.filter((node2) => node2.id !== node1.id)
|
||||
.map((node2) => (
|
||||
<motion.line
|
||||
key={`${node1.id}-${node2.id}`}
|
||||
x1={`${node1.baseX}%`}
|
||||
y1={`${node1.baseY}%`}
|
||||
x2={`${node2.baseX}%`}
|
||||
y2={`${node2.baseY}%`}
|
||||
stroke="rgba(255, 215, 0, 0.15)"
|
||||
strokeWidth="1"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: [0, 0.5, 0],
|
||||
x1: [`${node1.baseX}%`, `${node1.baseX + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
y1: [`${node1.baseY}%`, `${node1.baseY + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
x2: [`${node2.baseX}%`, `${node2.baseX + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
y2: [`${node2.baseY}%`, `${node2.baseY + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
repeatType: "mirror",
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</svg>
|
||||
|
||||
{nodes.map((node) => (
|
||||
<motion.div
|
||||
key={node.id}
|
||||
className="absolute w-3 h-3"
|
||||
style={{
|
||||
left: '-6px',
|
||||
top: '-6px',
|
||||
}}
|
||||
animate={{
|
||||
x: [
|
||||
`${node.baseX}%`,
|
||||
`${node.baseX + (Math.random() > 0.5 ? 10 : -10)}%`,
|
||||
`${node.baseX}%`,
|
||||
],
|
||||
y: [
|
||||
`${node.baseY}%`,
|
||||
`${node.baseY + (Math.random() > 0.5 ? 10 : -10)}%`,
|
||||
`${node.baseY}%`,
|
||||
],
|
||||
}}
|
||||
transition={{
|
||||
duration: 6,
|
||||
repeat: Infinity,
|
||||
repeatType: "reverse",
|
||||
ease: "easeInOut",
|
||||
delay: node.id * 0.2,
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute w-full h-full rounded-full bg-yellow-500"
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.7, 1, 0.7],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
className="absolute w-full h-full rounded-full bg-yellow-500/30"
|
||||
animate={{
|
||||
scale: [1, 3, 1],
|
||||
opacity: [0.3, 0, 0.3],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
delay: node.id * 0.1,
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
switch (type) {
|
||||
case 'network':
|
||||
return <NetworkAnimation className={className} />;
|
||||
case 'meme':
|
||||
return <MemeAnimation className={className} />;
|
||||
case 'news':
|
||||
return <NewsAnimation className={className} />;
|
||||
case 'community':
|
||||
return <CommunityAnimation className={className} />;
|
||||
case 'expert':
|
||||
return <ExpertAnimation className={className} />;
|
||||
case 'podcast':
|
||||
return <PodcastAnimation className={className} />;
|
||||
default:
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-yellow-500">
|
||||
Strategy Visualization
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (animation.type === 'meme') {
|
||||
const createWave = (waveIndex: number) => {
|
||||
return Array.from({ length: 6 }, (_, i) => {
|
||||
const symbols = ['😂', '🤔', '💭', '🎯', '🔥', '💯', '👀', '🙌', '✨', '💪'];
|
||||
const x = 5 + (i * (90 / 5));
|
||||
return {
|
||||
id: waveIndex * 6 + i,
|
||||
symbol: symbols[Math.floor(Math.random() * symbols.length)],
|
||||
x,
|
||||
y: 120 + (waveIndex * 60),
|
||||
rotation: -10 + Math.random() * 20, // Increased rotation range
|
||||
scale: 0.8 + Math.random() * 0.4, // More variation in scale
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const memeSymbols: MemeSymbol[] = [
|
||||
...createWave(0),
|
||||
...createWave(1),
|
||||
...createWave(2),
|
||||
...createWave(3),
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
{memeSymbols.map((meme) => (
|
||||
<motion.div
|
||||
key={meme.id}
|
||||
className="absolute text-2xl select-none"
|
||||
style={{
|
||||
transform: 'translate(-50%, -50%)',
|
||||
willChange: 'transform',
|
||||
}}
|
||||
initial={{
|
||||
x: `${meme.x}%`,
|
||||
y: `${meme.y}%`,
|
||||
rotate: meme.rotation,
|
||||
scale: meme.scale,
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
y: [`${meme.y}%`, '-20%'],
|
||||
rotate: meme.rotation,
|
||||
scale: meme.scale,
|
||||
opacity: [0, 1, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 6, // Faster movement
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
opacity: {
|
||||
times: [0, 0.1, 0.9, 1],
|
||||
duration: 6,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{meme.symbol}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
55
src/components/game/animations/CommunityAnimation.tsx
Обычный файл
55
src/components/game/animations/CommunityAnimation.tsx
Обычный файл
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export const CommunityAnimation = ({ className = '' }: { className?: string }) => {
|
||||
const groups = Array.from({ length: 3 }, (_, i) => ({
|
||||
x: 25 + i * 25,
|
||||
y: 50,
|
||||
members: Array.from({ length: 8 }, (_, j) => ({
|
||||
id: i * 8 + j,
|
||||
initialX: Math.random() * 100,
|
||||
initialY: Math.random() * 100,
|
||||
}))
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
{groups.map((group, groupIndex) => (
|
||||
<React.Fragment key={groupIndex}>
|
||||
{group.members.map((member) => (
|
||||
<motion.div
|
||||
key={member.id}
|
||||
className="absolute w-3 h-3 bg-yellow-500 rounded-full"
|
||||
initial={{
|
||||
x: `${member.initialX}%`,
|
||||
y: `${member.initialY}%`,
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
x: `${group.x}%`,
|
||||
y: `${group.y}%`,
|
||||
opacity: 1,
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
delay: member.id * 0.2,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<motion.div
|
||||
className="absolute w-20 h-20 rounded-full border-2 border-yellow-500/30"
|
||||
style={{
|
||||
x: `${group.x}%`,
|
||||
y: `${group.y}%`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ delay: 1.5, duration: 1 }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
49
src/components/game/animations/ExpertAnimation.tsx
Обычный файл
49
src/components/game/animations/ExpertAnimation.tsx
Обычный файл
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export const ExpertAnimation = ({ className = '' }: { className?: string }) => {
|
||||
const symbols = ['∑', '∫', 'π', '∞', '≠', '±', '∂', '∇', '∆'];
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
{symbols.map((symbol, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="absolute text-2xl font-bold text-yellow-500"
|
||||
initial={{
|
||||
x: `${Math.random() * 100}%`,
|
||||
y: "120%",
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
y: ["-20%", "120%"],
|
||||
opacity: [0, 1, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
delay: index * 0.5,
|
||||
ease: "easeInOut",
|
||||
opacity: {
|
||||
times: [0, 0.1, 0.9, 1],
|
||||
}
|
||||
}}
|
||||
>
|
||||
{symbol}
|
||||
</motion.div>
|
||||
))}
|
||||
<motion.div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: [0, 1, 0] }}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
<div className="text-4xl text-yellow-500 font-bold">PhD</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
74
src/components/game/animations/MemeAnimation.tsx
Обычный файл
74
src/components/game/animations/MemeAnimation.tsx
Обычный файл
@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface MemeSymbol {
|
||||
id: number;
|
||||
symbol: string;
|
||||
x: number;
|
||||
y: number;
|
||||
rotation: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export const MemeAnimation = ({ className = '' }: { className?: string }) => {
|
||||
const createWave = (waveIndex: number) => {
|
||||
return Array.from({ length: 6 }, (_, i) => {
|
||||
const symbols = ['😂', '🤔', '💭', '🎯', '🔥', '💯', '👀', '🙌', '✨', '💪'];
|
||||
const x = 5 + (i * (90 / 5));
|
||||
return {
|
||||
id: waveIndex * 6 + i,
|
||||
symbol: symbols[Math.floor(Math.random() * symbols.length)],
|
||||
x,
|
||||
y: 120 + (waveIndex * 60),
|
||||
rotation: -10 + Math.random() * 20,
|
||||
scale: 0.8 + Math.random() * 0.4,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const memeSymbols: MemeSymbol[] = [
|
||||
...createWave(0),
|
||||
...createWave(1),
|
||||
...createWave(2),
|
||||
...createWave(3),
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
{memeSymbols.map((meme) => (
|
||||
<motion.div
|
||||
key={meme.id}
|
||||
className="absolute text-2xl select-none"
|
||||
style={{
|
||||
transform: 'translate(-50%, -50%)',
|
||||
willChange: 'transform',
|
||||
}}
|
||||
initial={{
|
||||
x: `${meme.x}%`,
|
||||
y: `${meme.y}%`,
|
||||
rotate: meme.rotation,
|
||||
scale: meme.scale,
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
y: [`${meme.y}%`, '-20%'],
|
||||
rotate: meme.rotation,
|
||||
scale: meme.scale,
|
||||
opacity: [0, 1, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 6,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
opacity: {
|
||||
times: [0, 0.1, 0.9, 1],
|
||||
duration: 6,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{meme.symbol}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
113
src/components/game/animations/NetworkAnimation.tsx
Обычный файл
113
src/components/game/animations/NetworkAnimation.tsx
Обычный файл
@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface Node {
|
||||
id: number;
|
||||
baseX: number;
|
||||
baseY: number;
|
||||
}
|
||||
|
||||
export const NetworkAnimation = ({ className = '' }: { className?: string }) => {
|
||||
const nodes: Node[] = Array.from({ length: 6 }, (_, i) => {
|
||||
const row = Math.floor(i / 2);
|
||||
const col = i % 2;
|
||||
return {
|
||||
id: i,
|
||||
baseX: 30 + col * 40,
|
||||
baseY: 25 + row * 25,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
<svg className="absolute inset-0 w-full h-full pointer-events-none">
|
||||
{nodes.map((node1) =>
|
||||
nodes
|
||||
.filter((node2) => node2.id !== node1.id)
|
||||
.map((node2) => (
|
||||
<motion.line
|
||||
key={`${node1.id}-${node2.id}`}
|
||||
x1={`${node1.baseX}%`}
|
||||
y1={`${node1.baseY}%`}
|
||||
x2={`${node2.baseX}%`}
|
||||
y2={`${node2.baseY}%`}
|
||||
stroke="rgba(255, 215, 0, 0.15)"
|
||||
strokeWidth="1"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: [0, 0.5, 0],
|
||||
x1: [`${node1.baseX}%`, `${node1.baseX + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
y1: [`${node1.baseY}%`, `${node1.baseY + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
x2: [`${node2.baseX}%`, `${node2.baseX + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
y2: [`${node2.baseY}%`, `${node2.baseY + (Math.random() > 0.5 ? 10 : -10)}%`],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
repeatType: "mirror",
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</svg>
|
||||
|
||||
{nodes.map((node) => (
|
||||
<motion.div
|
||||
key={node.id}
|
||||
className="absolute w-3 h-3"
|
||||
style={{
|
||||
left: '-6px',
|
||||
top: '-6px',
|
||||
}}
|
||||
animate={{
|
||||
x: [
|
||||
`${node.baseX}%`,
|
||||
`${node.baseX + (Math.random() > 0.5 ? 10 : -10)}%`,
|
||||
`${node.baseX}%`,
|
||||
],
|
||||
y: [
|
||||
`${node.baseY}%`,
|
||||
`${node.baseY + (Math.random() > 0.5 ? 10 : -10)}%`,
|
||||
`${node.baseY}%`,
|
||||
],
|
||||
}}
|
||||
transition={{
|
||||
duration: 6,
|
||||
repeat: Infinity,
|
||||
repeatType: "reverse",
|
||||
ease: "easeInOut",
|
||||
delay: node.id * 0.2,
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute w-full h-full rounded-full bg-yellow-500"
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.7, 1, 0.7],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
className="absolute w-full h-full rounded-full bg-yellow-500/30"
|
||||
animate={{
|
||||
scale: [1, 3, 1],
|
||||
opacity: [0.3, 0, 0.3],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
delay: node.id * 0.1,
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
36
src/components/game/animations/NewsAnimation.tsx
Обычный файл
36
src/components/game/animations/NewsAnimation.tsx
Обычный файл
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export const NewsAnimation = ({ className = '' }: { className?: string }) => {
|
||||
const headlines = [
|
||||
"BREAKING NEWS • MATHEMATICAL TRUTH QUESTIONED •",
|
||||
"EXPERTS DIVIDED ON BASIC ARITHMETIC •",
|
||||
"NEW STUDY CHALLENGES CONVENTIONAL MATH •",
|
||||
"MATHEMATICAL REVOLUTION BREWING •",
|
||||
"EDUCATION SYSTEM IN CRISIS •"
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
{headlines.map((headline, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="absolute whitespace-nowrap text-yellow-500 font-bold"
|
||||
style={{
|
||||
top: `${index * 20}%`,
|
||||
}}
|
||||
initial={{ x: "100%" }}
|
||||
animate={{ x: "-100%" }}
|
||||
transition={{
|
||||
duration: 20,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
delay: index * 2,
|
||||
}}
|
||||
>
|
||||
{headline}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
44
src/components/game/animations/PodcastAnimation.tsx
Обычный файл
44
src/components/game/animations/PodcastAnimation.tsx
Обычный файл
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export const PodcastAnimation = ({ className = '' }: { className?: string }) => {
|
||||
const waves = Array.from({ length: 10 }, (_, i) => ({
|
||||
id: i,
|
||||
height: 20 + Math.random() * 60,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
|
||||
<div className="absolute bottom-0 left-0 right-0 flex justify-around items-end h-full">
|
||||
{waves.map((wave) => (
|
||||
<motion.div
|
||||
key={wave.id}
|
||||
className="w-2 bg-yellow-500 rounded-t-full"
|
||||
initial={{ height: 5 }}
|
||||
animate={{
|
||||
height: [5, wave.height, 5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 1,
|
||||
repeat: Infinity,
|
||||
delay: wave.id * 0.1,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<motion.div
|
||||
className="absolute top-1/4 left-1/2 transform -translate-x-1/2 text-4xl"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: [0, 1, 0] }}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
🎙️
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Загрузка…
x
Ссылка в новой задаче
Block a user