Rehab_React_Vite/src/pages/Welcome.tsx

224 lines
11 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 { 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>
)
}