230 lines
9.4 KiB
TypeScript
230 lines
9.4 KiB
TypeScript
"use client"
|
||
import type React from "react"
|
||
import { useState, useEffect } from "react"
|
||
import { useHistory } from "react-router-dom"
|
||
|
||
import BottomNavigation from "../components/BottomNavigation"
|
||
import HeaderNav from "../components/HeaderNav"
|
||
import video from "../assets/video.mov"
|
||
|
||
|
||
const Exercise: React.FC = () => {
|
||
const history = useHistory()
|
||
// const { id } = useParams<{ id: string }>()
|
||
const [isPlaying, setIsPlaying] = useState(false)
|
||
const [currentTime, setCurrentTime] = useState(0)
|
||
const [totalTime] = useState(900) // 15 minutes in seconds
|
||
const [currentSet, setCurrentSet] = useState(1)
|
||
const [totalSets] = useState(3)
|
||
|
||
useEffect(() => {
|
||
let interval: NodeJS.Timeout | undefined
|
||
if (isPlaying) {
|
||
interval = setInterval(() => {
|
||
setCurrentTime((prev) => {
|
||
if (prev >= totalTime) {
|
||
setIsPlaying(false)
|
||
// Show completion animation
|
||
history.push("/course-complete")
|
||
return totalTime
|
||
}
|
||
return prev + 1
|
||
})
|
||
}, 1000)
|
||
}
|
||
return () => {
|
||
if (interval) clearInterval(interval)
|
||
}
|
||
}, [isPlaying, totalTime, history])
|
||
|
||
const formatTime = (seconds: number) => {
|
||
const mins = Math.floor(seconds / 60)
|
||
const secs = seconds % 60
|
||
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`
|
||
}
|
||
|
||
const progress = (currentTime / totalTime) * 100
|
||
|
||
|
||
|
||
const PlayIcon = () => (
|
||
<svg className="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
|
||
<path d="M8 5v14l11-7z" />
|
||
</svg>
|
||
)
|
||
|
||
const PauseIcon = () => (
|
||
<svg className="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
|
||
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
|
||
</svg>
|
||
)
|
||
|
||
const RefreshIcon = () => (
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#6F6F6F" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> <path d="M2.5 2v6h6M21.5 22v-6h-6" /><path d="M22 11.5A10 10 0 0 0 3.2 7.2M2 12.5a10 10 0 0 0 18.8 4.2" /></svg>
|
||
)
|
||
|
||
const CheckIcon = () => (
|
||
<svg className="w-6 h-6 text-cyan-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||
</svg>
|
||
)
|
||
|
||
const exerciseSteps = [
|
||
{
|
||
title: "Исходное положение",
|
||
description:
|
||
"Лягте на спирку на коврик. Руки вдоль тела, ладони прижаты к полу. Ноги выпрямлены, носки направлены вверх. Поясница плотно прижата к полу.",
|
||
},
|
||
{
|
||
title: "Задание",
|
||
description:
|
||
"Медленно поднимите прямые ноги до угла 90 градусов. Задержитесь на 2 секунды, затем медленно опустите ноги, не касаясь пола. Повторите движение плавно и контролируемо.",
|
||
},
|
||
{
|
||
title: "Подходы",
|
||
description: "Выполните 3 подхода по 12 повторений с отдыхом 60 секунд между подходами.",
|
||
},
|
||
{
|
||
title: "Перерыв",
|
||
description: "Отдыхайте 60 секунд между подходами. Дышите спокойно и расслабьте мышцы.",
|
||
},
|
||
{
|
||
title: "Динамика и статика",
|
||
description:
|
||
"Динамическая фаза: подъем и опускание ног выполняется плавно, 2 секунды вверх, 2 секунды вниз. Статическая фаза: удержание ног в верхней точке на 2 секунды.",
|
||
},
|
||
]
|
||
|
||
return (
|
||
|
||
<div className="bg-gray-50 w-full h-full overflow-auto">
|
||
<div className="mt-36 mb-90 min-h-screen max-w-4xl mx-auto">
|
||
|
||
<HeaderNav item='Надо разобраться?' text='упражнение' />
|
||
|
||
|
||
|
||
|
||
{/* Video/Image Section */}
|
||
<div className="px-4 sm:px-6 mt-10 mb-6">
|
||
<div className="glass-morphism rounded-3xl overflow-hidden shadow-2xl border border-white/20 backdrop-blur-2xl">
|
||
<div className="relative">
|
||
<video
|
||
src={video}
|
||
className="w-full h-120 object-cover"
|
||
autoPlay
|
||
loop
|
||
muted
|
||
playsInline
|
||
/>
|
||
<div className="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-black/10"></div>
|
||
<div className="absolute inset-0 flex items-center justify-center">
|
||
<button
|
||
onClick={() => setIsPlaying(!isPlaying)}
|
||
className={
|
||
isPlaying
|
||
? "w-20 h-20 bg-transparent backdrop-blur-xl rounded-full flex items-center justify-center shadow-2xl transition-all duration-300 transform hover:scale-110 border border-cyan-50 opacity-40"
|
||
: "rounded-full w-20 h-20 flex items-center justify-center backdrop-blur-xl opacity-70"
|
||
}
|
||
>
|
||
{isPlaying ? <PauseIcon /> : <PlayIcon />}
|
||
</button>
|
||
</div>
|
||
{/* Live indicators */}
|
||
{isPlaying && (
|
||
<div className="absolute top-4 left-4 flex items-center space-x-2">
|
||
<div className="w-3 h-3 bg-orange-500 rounded-full animate-pulse "></div>
|
||
<span className="text-white text-sm font-bold bg-black/30 px-3 py-1 rounded-full backdrop-blur-sm">
|
||
Выполнение
|
||
</span>
|
||
</div>
|
||
)}
|
||
{/* Timer Display */}
|
||
<div className="absolute top-4 right-4 bg-cyan-50 backdrop-blur-sm px-3 py-1 rounded-xl">
|
||
<span className="text-gray-800 text-sm font-bold">{formatTime(currentTime)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{/* Exercise Steps - Vertical Scroll */}
|
||
<div className="px-4 sm:px-6 space-y-4 mb-6">
|
||
{exerciseSteps.map((step, index) => (
|
||
<div
|
||
key={index}
|
||
className="bg-white rounded-2xl p-5 border border-gray-200 shadow-lg hover:shadow-xl transition-all duration-300"
|
||
>
|
||
<div className="flex items-start space-x-4">
|
||
|
||
<div>
|
||
<h3 className="text-lg font-black text-gray-800 mb-2">{step.title}</h3>
|
||
<p className="text-gray-600 leading-relaxed text-sm sm:text-base">{step.description}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Fixed Timer at Bottom */}
|
||
<div className="fixed bottom-36 left-0 right-0 bg-white opacity-95 border-t border-white/20 px-4 sm:px-6 py-4 shadow-2xl z-30">
|
||
<div className="max-w-md mx-auto">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<div className="flex items-center space-x-2">
|
||
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
||
<span className="text-sm font-bold text-gray-700">
|
||
Подход {currentSet} из {totalSets}
|
||
</span>
|
||
</div>
|
||
<span className="text-sm font-black text-gray-700">
|
||
{formatTime(currentTime)} / {formatTime(totalTime)}
|
||
</span>
|
||
</div>
|
||
<div className="bg-white/30 rounded-full h-3 mb-4 overflow-hidden">
|
||
<div
|
||
className="bg-gradient-to-r from-[#2BACBE] via-cyan-500 to-cyan-700 h-3 rounded-full transition-all duration-1000 shadow-sm"
|
||
style={{ width: `${progress}%` }}
|
||
></div>
|
||
</div>
|
||
<div className="flex space-x-3">
|
||
<button
|
||
onClick={() => setIsPlaying(!isPlaying)}
|
||
className={`flex-1 font-bold py-3 px-4 rounded-xl transition-all duration-300 transform hover:scale-105 flex items-center justify-center space-x-2 ${isPlaying
|
||
? "bg-gray-400 text-white shadow-lg"
|
||
: "bg-[#2BACBE] hover:bg-[#2099A8] text-white shadow-lg"
|
||
}`}
|
||
>
|
||
{isPlaying ? (
|
||
<>
|
||
<PauseIcon /> <span>Пауза</span>
|
||
</>
|
||
) : (
|
||
<>
|
||
<PlayIcon /> <span className="backdrop-blur-xl">Начать</span>
|
||
</>
|
||
)}
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
setCurrentTime(0)
|
||
setIsPlaying(false)
|
||
}}
|
||
className="px-6 py-3 bg-white text-gray-800 font-bold rounded-xl transition-all duration-300 hover:shadow-lg border border-gray-200 flex items-center justify-center"
|
||
>
|
||
<RefreshIcon />
|
||
</button>
|
||
<button
|
||
onClick={() => setCurrentSet((prev) => Math.min(prev + 1, totalSets))}
|
||
className="px-6 py-3 bg-white text-gray-800 font-bold rounded-xl transition-all duration-300 hover:shadow-lg border border-gray-200 flex items-center justify-center"
|
||
>
|
||
<CheckIcon />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<BottomNavigation />
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default Exercise
|