feat: animated SVG weather icons + dynamic gradient background by weather/time
Some checks failed
Deploy / deploy (push) Failing after 2m6s
Some checks failed
Deploy / deploy (push) Failing after 2m6s
This commit is contained in:
151
components/WeatherAnimation.tsx
Normal file
151
components/WeatherAnimation.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
'use client'
|
||||
|
||||
interface WeatherAnimationProps {
|
||||
condition: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
function getCondition(desc: string): 'clear' | 'partly' | 'cloudy' | 'rain' | 'snow' | 'thunder' | 'fog' {
|
||||
const d = desc?.toLowerCase() || ''
|
||||
if (d.includes('гроз')) return 'thunder'
|
||||
if (d.includes('снег') || d.includes('снегопад')) return 'snow'
|
||||
if (d.includes('дождь') || d.includes('ливен') || d.includes('морос')) return 'rain'
|
||||
if (d.includes('туман')) return 'fog'
|
||||
if (d.includes('пасмурн')) return 'cloudy'
|
||||
if (d.includes('облач') || d.includes('перем')) return 'partly'
|
||||
return 'clear'
|
||||
}
|
||||
|
||||
export default function WeatherAnimation({ condition, size = 64 }: WeatherAnimationProps) {
|
||||
const c = getCondition(condition)
|
||||
const s = size
|
||||
|
||||
return (
|
||||
<div style={{ width: s, height: s, position: 'relative', flexShrink: 0 }}>
|
||||
<svg viewBox="0 0 100 100" width={s} height={s} style={{ overflow: 'visible' }}>
|
||||
{/* Sun */}
|
||||
{(c === 'clear' || c === 'partly') && (
|
||||
<g style={{ transformOrigin: c === 'partly' ? '35px 40px' : '50px 50px' }}>
|
||||
<circle
|
||||
cx={c === 'partly' ? 35 : 50}
|
||||
cy={c === 'partly' ? 40 : 50}
|
||||
r={c === 'partly' ? 14 : 18}
|
||||
fill="#fbbf24"
|
||||
style={{ filter: 'drop-shadow(0 0 8px rgba(251,191,36,0.6))' }}
|
||||
/>
|
||||
{/* Rays */}
|
||||
{[0,45,90,135,180,225,270,315].map(angle => (
|
||||
<line
|
||||
key={angle}
|
||||
x1={c === 'partly' ? 35 : 50}
|
||||
y1={c === 'partly' ? 40 : 50}
|
||||
x2={c === 'partly' ? 35 + Math.cos(angle * Math.PI / 180) * 22 : 50 + Math.cos(angle * Math.PI / 180) * 28}
|
||||
y2={c === 'partly' ? 40 + Math.sin(angle * Math.PI / 180) * 22 : 50 + Math.sin(angle * Math.PI / 180) * 28}
|
||||
stroke="#fbbf24"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
opacity={0.6}
|
||||
style={{
|
||||
transformOrigin: `${c === 'partly' ? 35 : 50}px ${c === 'partly' ? 40 : 50}px`,
|
||||
animation: `spin-slow 12s linear infinite`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
)}
|
||||
|
||||
{/* Cloud */}
|
||||
{(c === 'partly' || c === 'cloudy' || c === 'rain' || c === 'snow' || c === 'thunder') && (
|
||||
<g style={{ animation: 'cloud-float 4s ease-in-out infinite' }}>
|
||||
<ellipse cx={58} cy={52} rx={22} ry={14} fill="rgba(200,210,230,0.9)" />
|
||||
<ellipse cx={45} cy={48} rx={16} ry={12} fill="rgba(210,220,235,0.95)" />
|
||||
<ellipse cx={68} cy={50} rx={14} ry={10} fill="rgba(195,205,225,0.85)" />
|
||||
<ellipse cx={52} cy={56} rx={20} ry={10} fill="rgba(205,215,232,0.9)" />
|
||||
</g>
|
||||
)}
|
||||
|
||||
{/* Rain drops */}
|
||||
{(c === 'rain' || c === 'thunder') && (
|
||||
<g>
|
||||
{[
|
||||
{ x: 42, delay: 0 },
|
||||
{ x: 52, delay: 0.3 },
|
||||
{ x: 62, delay: 0.6 },
|
||||
{ x: 47, delay: 0.9 },
|
||||
{ x: 57, delay: 0.15 },
|
||||
].map((drop, i) => (
|
||||
<line
|
||||
key={i}
|
||||
x1={drop.x} y1={68} x2={drop.x - 2} y2={76}
|
||||
stroke="#60a5fa"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
opacity={0.7}
|
||||
style={{
|
||||
animation: `rain-fall 0.8s ease-in infinite`,
|
||||
animationDelay: `${drop.delay}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
)}
|
||||
|
||||
{/* Thunder bolt */}
|
||||
{c === 'thunder' && (
|
||||
<polygon
|
||||
points="55,55 50,70 56,68 52,82 62,64 56,66 60,55"
|
||||
fill="#fbbf24"
|
||||
style={{
|
||||
animation: 'thunder-flash 2s ease-in-out infinite',
|
||||
filter: 'drop-shadow(0 0 4px rgba(251,191,36,0.8))',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Snow flakes */}
|
||||
{c === 'snow' && (
|
||||
<g>
|
||||
{[
|
||||
{ x: 42, delay: 0 },
|
||||
{ x: 52, delay: 0.4 },
|
||||
{ x: 62, delay: 0.8 },
|
||||
{ x: 47, delay: 1.2 },
|
||||
{ x: 57, delay: 0.2 },
|
||||
].map((flake, i) => (
|
||||
<circle
|
||||
key={i}
|
||||
cx={flake.x} cy={72}
|
||||
r={2.5}
|
||||
fill="white"
|
||||
opacity={0.8}
|
||||
style={{
|
||||
animation: `snow-fall 2s ease-in-out infinite`,
|
||||
animationDelay: `${flake.delay}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
)}
|
||||
|
||||
{/* Fog lines */}
|
||||
{c === 'fog' && (
|
||||
<g>
|
||||
{[40, 52, 64].map((y, i) => (
|
||||
<line
|
||||
key={i}
|
||||
x1={25} y1={y} x2={75} y2={y}
|
||||
stroke="rgba(200,210,230,0.5)"
|
||||
strokeWidth={4}
|
||||
strokeLinecap="round"
|
||||
style={{
|
||||
animation: `fog-drift 3s ease-in-out infinite`,
|
||||
animationDelay: `${i * 0.5}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user