224 lines
11 KiB
TypeScript
224 lines
11 KiB
TypeScript
"use client"
|
||
|
||
import { useEffect, useState } from "react"
|
||
|
||
export default function Welcome() {
|
||
const [animationPhase, setAnimationPhase] = useState(0)
|
||
const [displayedTitle, setDisplayedTitle] = useState("")
|
||
const [displayedSubtitle, setDisplayedSubtitle] = useState("")
|
||
const [loaderWidth, setLoaderWidth] = useState(0) // Изначально 0 для анимации слева направо
|
||
|
||
const fullTitle = "Реабилитация"
|
||
const fullSubtitle = "Восстановление через движение"
|
||
|
||
useEffect(() => {
|
||
// Phase 1: App name appears, ball appears (after 0.2s)
|
||
const timer1 = setTimeout(() => setAnimationPhase(1), 700)
|
||
// Phase 2: Limbs start growing (after 0.7s)
|
||
// Limb growth animation is 2s, so it finishes at 0.7s + 2s = 2.7s
|
||
const timer2 = setTimeout(() => setAnimationPhase(2), 700)
|
||
// Phase 3: SVG rotates, ball tosses (after 1.7s, when limbs are fully grown)
|
||
const timer3 = setTimeout(() => setAnimationPhase(3), 1700)
|
||
// Phase 4: Subtitle and Loader appear (after ball toss finishes)
|
||
// Ball toss animation is 1.2s with 0.1s delay, so it finishes around 1.7s + 0.1s + 1.2s = 3.0s
|
||
// Starting Phase 4 at 3.1s ensures ball is completely gone
|
||
const timer4 = setTimeout(() => setAnimationPhase(4), 3100) // Запускаем фазу 4 после исчезновения мяча
|
||
// Phase 5: Ball starts bouncing (after subtitle and loader appear, e.g., 3.1s + 1s for subtitle opacity = 4.1s, so start at 4.2s)
|
||
const timer5 = setTimeout(() => setAnimationPhase(5), 3100)
|
||
|
||
return () => {
|
||
clearTimeout(timer1)
|
||
clearTimeout(timer2)
|
||
clearTimeout(timer3)
|
||
clearTimeout(timer4)
|
||
clearTimeout(timer5)
|
||
}
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
if (animationPhase >= 1) {
|
||
setDisplayedTitle(fullTitle) // Заголовок появляется мгновенно
|
||
}
|
||
if (animationPhase >= 4) { // Подзаголовок и лоадер появляются в фазе 4
|
||
setDisplayedSubtitle(fullSubtitle) // Подзаголовок появляется целиком
|
||
setLoaderWidth(100) // Лоадер анимируется до полной ширины
|
||
}
|
||
}, [animationPhase]) // Зависимость от animationPhase
|
||
|
||
return (
|
||
<div className="min-h-screen relative overflow-hidden">
|
||
{/* Background and particles */}
|
||
<div className="absolute inset-0 bg-gradient-to-br from-[#3ABBC7] to-[#0D212C]">
|
||
{/* Floating particles */}
|
||
<div className="absolute inset-0">
|
||
<div className="absolute top-20 left-10 w-32 h-32 bg-white/5 rounded-full animate-pulse blur-xl"></div>
|
||
<div className="absolute top-1/2 left-1/4 w-16 h-16 bg-white/5 rounded-full animate-ping blur-md"></div>
|
||
<div className="absolute top-1/3 right-1/3 w-20 h-20 bg-white/5 rounded-full animate-pulse blur-lg"></div>
|
||
</div>
|
||
{/* SVG with animated figure */}
|
||
<svg
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
viewBox="0 -10 64 74" /* Adjusted viewBox to prevent clipping */
|
||
className={`h-[30rem] lg:h-[50rem] w-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-0 ${animationPhase >= 3 ? 'svg-rotate-active' : ''}`}
|
||
>
|
||
<defs>
|
||
<style>{`
|
||
/* SVG Rotation */
|
||
@keyframes svgRotate {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(30deg); }
|
||
}
|
||
.svg-rotate-active {
|
||
animation: svgRotate 0.8s ease-out forwards; /* Faster rotation */
|
||
}
|
||
/* Limb growth animation for ARMS (paths) */
|
||
.arm-limb {
|
||
stroke-dasharray: var('--length');
|
||
stroke-dashoffset: var('--length'); /* Initially hidden */
|
||
}
|
||
.arm-limb.limb-grow-active {
|
||
animation: drawLine 0.5s linear forwards; /* 2s linear для синхронизации */
|
||
}
|
||
@keyframes drawLine {
|
||
to {
|
||
stroke-dashoffset: 0;
|
||
}
|
||
}
|
||
/* Limb growth animation for LEGS (rects) */
|
||
.leg-limb {
|
||
transform-origin: top; /* Рост от верхней части (туловища) */
|
||
transform: scaleY(0); /* Initially hidden */
|
||
}
|
||
.leg-limb.limb-grow-active {
|
||
animation: growRect 0.4s linear forwards; /* 2s linear для синхронизации */
|
||
}
|
||
@keyframes growRect {
|
||
from {
|
||
transform: scaleY(0);
|
||
}
|
||
to {
|
||
transform: scaleY(1);
|
||
}
|
||
}
|
||
/* Counter-rotation for the tossing arm's group */
|
||
@keyframes counterRotateArm {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(-5deg); } /* Уменьшен угол наклона руки */
|
||
}
|
||
.tossing-arm-counter-rotate {
|
||
animation: counterRotateArm 0.8s ease-out forwards; /* Faster counter-rotation */
|
||
transform-origin: 34px 26px;
|
||
}
|
||
/* Ball animation */
|
||
.ball-toss-active {
|
||
animation: tossBall 1.2s ease-out forwards; /* Увеличена длительность для плавности */
|
||
animation-delay: 0.1s; /* Небольшая задержка после поворота руки */
|
||
}
|
||
@keyframes tossBall {
|
||
0% { transform: translate(-50%, -50%) scale(1,1); opacity: 0; } /* Начальное положение, центрирование */
|
||
10% { transform: translate(-50%, -50%) scale(1); opacity: 1; } /* Начальное положение, центрирование */
|
||
25% { transform: translate(calc(-50% + 30px), calc(-50% - 80px)) scale(1.1); opacity: 1; } /* Бросок вверх и вправо */
|
||
50% { transform: translate(calc(-50% + 40px), calc(-50% + 150px)) scale(1); opacity: 1; } /* Отскок вниз к ногам (имитация пола) */
|
||
70% { transform: translate(calc(-50% + 40px), calc(-50% + 150px)) scale(5); opacity: 1; } /* Начинает лететь на пользователя, увеличиваясь */
|
||
100% { transform: translate(calc(-50% + 40px), calc(-50% + 150px)) scale(200); opacity: 0; } /* Полностью летит на пользователя, исчезает */
|
||
}
|
||
/* New: Ball bounce animation */
|
||
@keyframes bounceBall {
|
||
0%, 100% { transform: translate(-50%, -50%) translateY(0); }
|
||
50% { transform: translate(-50%, -52%) translateY(-45px); } /* Adjust bounce height as needed */
|
||
}
|
||
.ball-bounce-active {
|
||
animation: bounceBall 0.8s ease-in-out infinite alternate; /* Smooth bounce, infinite, alternating */
|
||
}
|
||
`}</style>
|
||
</defs>
|
||
{/* Group with mirror transform for the human figure */}
|
||
<g transform="translate(64, 0) scale(-1, 1)">
|
||
{/* Голова */}
|
||
<circle cx={32} cy={12} r={6} fill="#1A3A4A" />
|
||
{/* Тело */}
|
||
<rect x={24} y={20} width={16} height={24} fill="#1A3A4A" rx={2} />
|
||
{/* Левая рука (смотрит вниз) - THIS IS THE TOSSING ARM AFTER MIRRORING */}
|
||
{/* Wrapped in a new group for counter-rotation */}
|
||
<g className={`${animationPhase >= 3 ? 'tossing-arm-counter-rotate' : ''}`}>
|
||
<path
|
||
d="M34,26 Q51,16 40,3"
|
||
fill="none"
|
||
stroke="#1A3A4A"
|
||
strokeWidth={6}
|
||
strokeLinecap="round"
|
||
className={`arm-limb ${animationPhase >= 2 ? 'limb-grow-active' : ''}`} /* Apply active class when phase 2 starts */
|
||
style={{ '--length': '70' }}
|
||
/>
|
||
</g>
|
||
{/* Правая рука (волнообразная вверх) - This arm will remain static after growth */}
|
||
<path
|
||
d="M26,23 Q16,34 18,32"
|
||
fill="none"
|
||
stroke="#1A3A4A"
|
||
strokeWidth={6}
|
||
strokeLinecap="round"
|
||
className={`arm-limb ${animationPhase >= 2 ? 'limb-grow-active' : ''}`} /* Apply active class when phase 2 starts */
|
||
style={{ '--length': '60' }}
|
||
/>
|
||
{/* Левая нога - длиннее */}
|
||
<rect
|
||
x={24}
|
||
y={40} /* Начало от нижней части туловища */
|
||
width={6}
|
||
height={20}
|
||
rx={3}
|
||
fill="#1A3A4A"
|
||
className={`leg-limb ${animationPhase >= 2 ? 'limb-grow-active' : ''}`} /* Apply active class when phase 2 starts */
|
||
/>
|
||
{/* Правая нога - длиннее */}
|
||
<rect
|
||
x={34}
|
||
y={40} /* Начало от нижней части туловища */
|
||
width={6}
|
||
height={13}
|
||
rx={3}
|
||
fill="#1A3A4A"
|
||
className={`leg-limb ${animationPhase >= 2 ? 'limb-grow-active' : ''}`} /* Apply active class when phase 2 starts */
|
||
/>
|
||
</g>
|
||
</svg>
|
||
{/* Ball - now a separate div for absolute positioning */}
|
||
<div
|
||
className={`absolute rounded-full bg-[#FFA500] w-[2.7rem] h-[2.7rem] z-20 transition-opacity duration-200
|
||
${animationPhase >= 1 ? 'opacity-100' : 'opacity-0'}
|
||
${animationPhase >= 3 ? 'ball-toss-active' : ''}
|
||
${animationPhase >= 5 ? 'ball-bounce-active' : ''}
|
||
`}
|
||
style={{
|
||
// Adjusted initial position using vh/vw for better responsiveness
|
||
top: 'calc(50% - 20vh)', // Примерно 20% высоты вьюпорта выше центра
|
||
left: '50%', // Примерно 3.8% ширины вьюпорта левее центра
|
||
transform: 'translate(-50%, -50%)', // Центрирует сам div относительно его top/left
|
||
}}
|
||
/>
|
||
{/* Main Content */}
|
||
<div className="flex items-center justify-center min-h-screen">
|
||
<div className="text-center px-8 z-10">
|
||
{/* App Name */}
|
||
<div
|
||
className={`transition-opacity duration-1000 delay-100 ${animationPhase >= 1 ? "opacity-100" : "opacity-0"}`}
|
||
>
|
||
<h1 className="text-5xl font-black text-white mb-4 tracking-tight filter drop-shadow-lg">{displayedTitle}</h1>
|
||
{/* Line Loader - now appears with subtitle in phase 4, and grows from left to right */}
|
||
<div
|
||
className={`h-1 bg-white/70 rounded-full mx-auto my-2 transition-all duration-500 ease-out`}
|
||
style={{ width: `${loaderWidth}%`, maxWidth: '200px', opacity: animationPhase >= 4 ? 1 : 0 }}
|
||
></div>
|
||
{/* Subtitle - appears with opacity transition in phase 4 */}
|
||
<p className={`text-white/90 text-xl font-medium tracking-wide transition-opacity duration-1000 ${animationPhase >= 4 ? 'opacity-100' : 'opacity-0'}`}>
|
||
{displayedSubtitle}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|