начальная анимация на загрузку

This commit is contained in:
Tatyana 2025-08-06 12:24:39 +03:00
parent 841bf404ec
commit cc145c8804
3 changed files with 267 additions and 177 deletions

View File

@ -1,133 +0,0 @@
"use client"
import { useEffect, useState } from "react"
import { useHistory } from "react-router-dom"
export default function Welcome() {
const history = useHistory()
const [animationPhase, setAnimationPhase] = useState(0)
useEffect(() => {
const timer1 = setTimeout(() => setAnimationPhase(1), 500)
const timer2 = setTimeout(() => setAnimationPhase(2), 1500)
const timer3 = setTimeout(() => setAnimationPhase(3), 2500)
// const timer4 = setTimeout(() => history.push("/login"), 4000) // Раскомментируйте при необходимости
return () => {
clearTimeout(timer1)
clearTimeout(timer2)
clearTimeout(timer3)
// clearTimeout(timer4)
}
}, [history])
return (
<div className="min-h-screen relative overflow-hidden">
{/* Фон и частицы */}
<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 bottom-32 right-16 w-24 h-24 bg-white/10 rounded-full animate-bounce blur-lg"></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>
{/* Основной контент */}
<div className="flex items-center justify-center min-h-screen">
<div className="text-center z-10 px-8">
{/* Заголовки и загрузка */}
{/* ... ваш существующий код ... */}
{/* SVG с анимацией конечностей */}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 64 64"
className="h-[30rem] lg:h-[50rem] w-auto z-50 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
>
{/* Группа с зеркальным отражением всего, кроме рук */}
<g transform="translate(64, 0) scale(-1, 1)">
{/* Голова */}
<circle cx={32} cy={12} r={6} fill="#000" />
{/* Тело */}
<rect x={24} y={20} width={16} height={24} fill="#000" rx={2} />
{/* Левая рука (смотрит вниз) */}
<path
d="M34,26 Q51,16 40,3"
fill="none"
stroke="#000"
strokeWidth={6}
strokeLinecap="round"
className={`limb`}
style={{ '--length': '70' }}
/>
{/* Правая рука (волнообразная вверх) */}
<path
d="M26,23 Q16,34 18,32"
fill="none"
stroke="#000"
strokeWidth={6}
strokeLinecap="round"
className={`limb`}
style={{ '--length': '60' }}
/>
{/* Левая нога - длиннее */}
<rect
x={24}
y={40}
width={6}
height={20}
rx={3}
className={`limb`}
style={{ '--length': '60' }}
/>
{/* Правая нога - длиннее */}
<rect
x={34}
y={40}
width={6}
height={13}
rx={3}
className={`limb`}
style={{ '--length': '50' }}
/>
</g>
{/* Внутри SVG добавим стили для анимации линий */}
<style>
{`
.limb {
stroke-dasharray: var(--length);
stroke-dashoffset: var(--length);
opacity: 0;
animation: growLine 2s forwards;
/* Все начинают одновременно */
}
/* Можно оставить задержки равными нулю или убрать их */
`}
</style>
{/* Анимация для всех элементов одновременно */}
<defs>
<style>
{`
@keyframes growLine {
to {
stroke-dashoffset: 0;
opacity: 1;
}
}
.limb {
animation-delay: 0s; /* все начинают сразу */
}
`}
</style>
</defs>
</svg>
</div>
</div>
</div>
</div>
)
}

View File

@ -1,75 +1,219 @@
"use client" "use client"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { useHistory } from "react-router-dom"
import manImage from "../assets/man.svg" // Reverted to original import
export default function Welcome() { export default function Welcome() {
const history = useHistory()
const [animationPhase, setAnimationPhase] = useState(0) const [animationPhase, setAnimationPhase] = useState(0)
const [displayedTitle, setDisplayedTitle] = useState("")
const [displayedSubtitle, setDisplayedSubtitle] = useState("")
const [loaderWidth, setLoaderWidth] = useState(0) // Изначально 0 для анимации слева направо
const fullTitle = "Реабилитация"
const fullSubtitle = "Восстановление через движение"
useEffect(() => { useEffect(() => {
const timer1 = setTimeout(() => setAnimationPhase(1), 500) // Phase 1: App name appears, ball appears (after 0.2s)
const timer2 = setTimeout(() => setAnimationPhase(2), 1500) const timer1 = setTimeout(() => setAnimationPhase(1), 700)
const timer3 = setTimeout(() => setAnimationPhase(3), 2500) // Phase 2: Limbs start growing (after 0.7s)
// const timer4 = setTimeout(() => history.push("/login"), 4000) // Uncomment if you want auto-redirect // 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 () => { return () => {
clearTimeout(timer1) clearTimeout(timer1)
clearTimeout(timer2) clearTimeout(timer2)
clearTimeout(timer3) clearTimeout(timer3)
// clearTimeout(timer4) clearTimeout(timer4)
clearTimeout(timer5)
} }
}, [history]) }, [])
useEffect(() => {
if (animationPhase >= 1) {
setDisplayedTitle(fullTitle) // Заголовок появляется мгновенно
}
if (animationPhase >= 4) { // Подзаголовок и лоадер появляются в фазе 4
setDisplayedSubtitle(fullSubtitle) // Подзаголовок появляется целиком
setLoaderWidth(100) // Лоадер анимируется до полной ширины
}
}, [animationPhase]) // Зависимость от animationPhase
return ( return (
<div className="min-h-screen relative overflow-hidden"> <div className="min-h-screen relative overflow-hidden">
<img {/* Background and particles */}
className="h-[30rem] lg:h-[50rem] w-auto z-50 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-15"
src={manImage || "/placeholder.svg"} // Reverted to original img tag and src
alt="Man illustration"
/>
{/* Uncomment and update path for emblemImage if needed */}
{/* <img
className="h-[30rem] lg:h-[0rem] w-auto z-50 absolute right-0 bottom-0 transform opacity-10"
src={emblemImage || "/placeholder.svg"} // Reverted to original img tag and src
alt="Emblem"
/> */}
<div className="absolute inset-0 bg-gradient-to-br from-[#3ABBC7] to-[#0D212C]"> <div className="absolute inset-0 bg-gradient-to-br from-[#3ABBC7] to-[#0D212C]">
{/* Floating particles */} {/* Floating particles */}
<div className="absolute inset-0"> <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-20 left-10 w-32 h-32 bg-white/5 rounded-full animate-pulse blur-xl"></div>
<div className="absolute bottom-32 right-16 w-24 h-24 bg-white/10 rounded-full animate-bounce blur-lg"></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/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 className="absolute top-1/3 right-1/3 w-20 h-20 bg-white/5 rounded-full animate-pulse blur-lg"></div>
</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 */} {/* Main Content */}
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
<div className="text-center z-10 px-8"> <div className="text-center px-8 z-10">
{/* App Name */} {/* App Name */}
<div <div
className={`transition-all duration-1000 delay-300 ${ className={`transition-opacity duration-1000 delay-100 ${animationPhase >= 1 ? "opacity-100" : "opacity-0"}`}
animationPhase >= 1 ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
}`}
> >
<h1 className="text-5xl font-black text-white mb-4 tracking-tight filter drop-shadow-lg">Реабилитация</h1> <h1 className="text-5xl font-black text-white mb-4 tracking-tight filter drop-shadow-lg">{displayedTitle}</h1>
<p className="text-white/90 text-xl font-medium tracking-wide">Восстановление через движение</p> {/* Line Loader - now appears with subtitle in phase 4, and grows from left to right */}
</div> <div
{/* Loading indicator */} className={`h-1 bg-white/70 rounded-full mx-auto my-2 transition-all duration-500 ease-out`}
<div style={{ width: `${loaderWidth}%`, maxWidth: '200px', opacity: animationPhase >= 4 ? 1 : 0 }}
className={`mt-16 transition-all duration-500 delay-700 ${ ></div>
animationPhase >= 1 ? "opacity-100" : "opacity-0" {/* 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}
<div className="w-64 h-3 bg-white/10 rounded-full mx-auto overflow-hidden backdrop-blur-sm border border-white/20"> </p>
<div
className="h-full bg-gradient-to-r from-white via-white/80 to-white rounded-full shadow-lg"
style={{
width: `${Math.min((animationPhase + 1) * 25, 100)}%`,
transition: "width 0.8s cubic-bezier(0.4, 0, 0.2, 1)",
}}
></div>
</div>
<p className="text-white/70 text-lg mt-6 font-medium">Загрузка...</p>
</div> </div>
</div> </div>
</div> </div>

79
src/pages/WelcomeOld.tsx Normal file
View File

@ -0,0 +1,79 @@
"use client"
import { useEffect, useState } from "react"
import { useHistory } from "react-router-dom"
import manImage from "../assets/man.svg" // Reverted to original import
export default function Welcome() {
const history = useHistory()
const [animationPhase, setAnimationPhase] = useState(0)
useEffect(() => {
const timer1 = setTimeout(() => setAnimationPhase(1), 500)
const timer2 = setTimeout(() => setAnimationPhase(2), 1500)
const timer3 = setTimeout(() => setAnimationPhase(3), 2500)
// const timer4 = setTimeout(() => history.push("/login"), 4000) // Uncomment if you want auto-redirect
return () => {
clearTimeout(timer1)
clearTimeout(timer2)
clearTimeout(timer3)
// clearTimeout(timer4)
}
}, [history])
return (
<div className="min-h-screen relative overflow-hidden">
<img
className="h-[30rem] lg:h-[50rem] w-auto z-50 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-15"
src={manImage || "/placeholder.svg"} // Reverted to original img tag and src
alt="Man illustration"
/>
{/* Uncomment and update path for emblemImage if needed */}
{/* <img
className="h-[30rem] lg:h-[0rem] w-auto z-50 absolute right-0 bottom-0 transform opacity-10"
src={emblemImage || "/placeholder.svg"} // Reverted to original img tag and src
alt="Emblem"
/> */}
<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 bottom-32 right-16 w-24 h-24 bg-white/10 rounded-full animate-bounce blur-lg"></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>
{/* Main Content */}
<div className="flex items-center justify-center min-h-screen">
<div className="text-center z-10 px-8">
{/* App Name */}
<div
className={`transition-all duration-1000 delay-300 ${
animationPhase >= 1 ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
}`}
>
<h1 className="text-5xl font-black text-white mb-4 tracking-tight filter drop-shadow-lg">Реабилитация</h1>
<p className="text-white/90 text-xl font-medium tracking-wide">Восстановление через движение</p>
</div>
{/* Loading indicator */}
<div
className={`mt-16 transition-all duration-500 delay-700 ${
animationPhase >= 1 ? "opacity-100" : "opacity-0"
}`}
>
<div className="w-64 h-3 bg-white/10 rounded-full mx-auto overflow-hidden backdrop-blur-sm border border-white/20">
<div
className="h-full bg-gradient-to-r from-white via-white/80 to-white rounded-full shadow-lg"
style={{
width: `${Math.min((animationPhase + 1) * 25, 100)}%`,
transition: "width 0.8s cubic-bezier(0.4, 0, 0.2, 1)",
}}
></div>
</div>
<p className="text-white/70 text-lg mt-6 font-medium">Загрузка...</p>
</div>
</div>
</div>
</div>
</div>
)
}