Compare commits
4 Commits
b414c0e8ab
...
28d20b23a0
Author | SHA1 | Date | |
---|---|---|---|
28d20b23a0 | |||
694b053a10 | |||
6e11357cfd | |||
848e4111c0 |
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Реабилитация</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
36
src/App.tsx
36
src/App.tsx
@ -1,38 +1,22 @@
|
|||||||
import React from 'react';
|
import type React from "react"
|
||||||
import { IonApp, IonRouterOutlet } from '@ionic/react';
|
import { IonApp, IonRouterOutlet } from "@ionic/react"
|
||||||
import { IonReactRouter } from '@ionic/react-router';
|
import { IonReactRouter } from "@ionic/react-router"
|
||||||
|
import AppRoutes from "./AppRoutes"
|
||||||
import AppRoutes from './AppRoutes';
|
import "./index.css"
|
||||||
|
import "@ionic/react/css/core.css"
|
||||||
import './index.css'
|
// import Header from "./components/Header"
|
||||||
import '@ionic/react/css/core.css';
|
// import Footer from "./components/Footer"
|
||||||
|
|
||||||
import Header from './components/Header';
|
|
||||||
import Footer from './components/Footer';
|
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<IonApp>
|
<IonApp>
|
||||||
<IonReactRouter>
|
<IonReactRouter>
|
||||||
|
|
||||||
{/* Постоянный Header */}
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
{/* Основной контейнер для контента */}
|
|
||||||
<div className='flex flex-col min-h-screen container mx-auto px-4 bg-fuchsia-200'>
|
|
||||||
|
|
||||||
|
|
||||||
<div className='container mt-20 flex-1'>
|
|
||||||
<IonRouterOutlet>
|
<IonRouterOutlet>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</IonRouterOutlet>
|
</IonRouterOutlet>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Постоянный Footer */}
|
|
||||||
<Footer />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</IonReactRouter>
|
</IonReactRouter>
|
||||||
</IonApp>
|
</IonApp>
|
||||||
);
|
)
|
||||||
|
|
||||||
export default App;
|
export default App
|
@ -1,16 +1,29 @@
|
|||||||
import React from 'react';
|
import { Route } from "react-router-dom"
|
||||||
import { Route } from 'react-router-dom';
|
import Home from "./pages/Home"
|
||||||
|
import Login from "./pages/Login"
|
||||||
import Home from './pages/Home';
|
import Register from "./pages/Register"
|
||||||
import Login from './pages/Login';
|
import Welcome from "./pages/Welcome"
|
||||||
import Register from './pages/Register';
|
import ForgotPassword from "./pages/ForgotPassword"
|
||||||
|
import Courses from "./pages/Courses"
|
||||||
|
import CourseExercises from "./pages/CourseExercises"
|
||||||
|
import Exercise from "./pages/Exercise"
|
||||||
|
import Settings from "./pages/Settings"
|
||||||
|
import CourseComplete from "./pages/CourseComplete"
|
||||||
|
|
||||||
const AppRoutes = () => (
|
const AppRoutes = () => (
|
||||||
<>
|
<>
|
||||||
|
<Route exact path="/" component={Welcome} />
|
||||||
<Route path="/home" component={Home} exact />
|
<Route path="/home" component={Home} exact />
|
||||||
<Route path="/register" component={Register} />
|
<Route path="/register" component={Register} />
|
||||||
<Route path="/login" component={Login} />
|
<Route path="/login" component={Login} />
|
||||||
|
<Route path="/forgot-password" component={ForgotPassword} />
|
||||||
|
<Route path="/welcome" component={Welcome} />
|
||||||
|
<Route path="/courses" component={Courses} />
|
||||||
|
<Route path="/course/:id/exercises" component={CourseExercises} />
|
||||||
|
<Route path="/exercise/:id" component={Exercise} />
|
||||||
|
<Route path="/settings" component={Settings} />
|
||||||
|
<Route path="/course-complete" component={CourseComplete} />
|
||||||
</>
|
</>
|
||||||
);
|
)
|
||||||
|
|
||||||
export default AppRoutes;
|
export default AppRoutes
|
||||||
|
BIN
src/assets/emblem.png
Normal file
BIN
src/assets/emblem.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
3
src/assets/man.svg
Normal file
3
src/assets/man.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="231" height="414" viewBox="0 0 231 414" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M52.6304 288.512L34.3361 320.212C31.8969 324.5 28.1648 327.336 23.14 328.72C18.1152 330.105 13.4623 329.566 9.18144 327.103C4.90057 324.641 2.08324 320.892 0.729464 315.857C-0.624315 310.822 -0.0937818 306.148 2.32107 301.835L87.3896 154.363C75.8032 142.419 67.1134 128.789 61.3202 113.475C55.527 98.1616 52.6304 82.3884 52.6304 66.1557C52.6304 58.1925 53.3195 50.1558 54.6977 42.0456C56.0759 33.9354 58.4358 26.0457 61.7776 18.3766C63.607 13.7824 66.961 10.7932 71.8395 9.40881C76.7179 8.02444 81.2915 8.56349 85.5602 11.026C89.8289 13.4884 92.6523 17.1637 94.0304 22.0519C95.4086 26.9401 95.3294 31.8405 93.7926 36.7532C92.2681 41.3473 91.1278 46.0211 90.3716 50.7745C89.6154 55.5279 89.2312 60.3487 89.2191 65.2369C89.2191 81.4695 93.1828 96.7099 101.11 110.958C109.038 125.206 120.319 136.612 134.955 145.175L176.117 169.065C195.021 180.091 208.894 195.943 217.737 216.623C226.579 237.303 231 256.672 231 274.73C231 282.999 230.238 291.195 228.713 299.318C227.189 307.44 224.902 315.324 221.853 322.969C220.023 327.869 216.669 331.011 211.791 332.396C206.912 333.78 202.186 333.241 197.613 330.779C193.344 328.328 190.448 324.653 188.923 319.753C187.399 314.852 187.399 309.799 188.923 304.592C190.448 299.692 191.594 294.865 192.362 290.111C193.131 285.358 193.509 280.537 193.497 275.649C193.497 265.848 192.125 256.353 189.38 247.165C186.636 237.977 182.215 229.401 176.117 221.438L70.01 404.744C67.5708 409.032 63.8388 411.868 58.8139 413.253C53.7891 414.637 49.1362 414.098 44.8553 411.636C40.5745 409.173 37.7572 405.424 36.4034 400.389C35.0496 395.354 35.5801 390.68 37.995 386.368L83.7308 306.889L52.6304 288.512ZM185.264 147.013C175.202 147.013 166.592 143.417 159.433 136.226C152.273 129.034 148.688 120.379 148.676 110.26C148.663 100.14 152.249 91.4909 159.433 84.3118C166.616 77.1327 175.227 73.5308 185.264 73.5063C195.302 73.4818 203.918 77.0837 211.114 84.3118C218.31 91.5399 221.889 100.189 221.853 110.26C221.816 120.33 218.237 128.985 211.114 136.226C203.991 143.466 195.375 147.062 185.264 147.013ZM139.528 55.1298C131.601 55.1298 125.045 52.3733 119.862 46.8603C114.679 41.3473 112.087 34.9155 112.087 27.5649C112.087 19.6017 114.831 13.0167 120.319 7.81005C125.808 2.60335 132.211 0 139.528 0C147.456 0 154.011 2.75649 159.195 8.26946C164.378 13.7824 166.97 20.2142 166.97 27.5649C166.97 35.5281 164.226 42.113 158.737 47.3197C153.249 52.5264 146.846 55.1298 139.528 55.1298Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
114
src/components/BottomNavigation.tsx
Normal file
114
src/components/BottomNavigation.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useHistory, useLocation } from "react-router-dom"
|
||||||
|
|
||||||
|
const BottomNavigation: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
const HomeIcon = ({ active }: { active: boolean }) => (
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" className={active ? "text-white" : "text-[#5F5C5C]"}>
|
||||||
|
<path
|
||||||
|
d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path d="M9 22V12H15V22" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
const CoursesIcon = ({ active }: { active: boolean }) => (
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" className={active ? "text-white" : "text-[#5F5C5C]"}>
|
||||||
|
<path
|
||||||
|
d="M2 3H8C9.06087 3 10.0783 3.42143 10.8284 4.17157C11.5786 4.92172 12 5.93913 12 7V21C12 20.2044 11.6839 19.4413 11.1213 18.8787C10.5587 18.3161 9.79565 18 9 18H2V3Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M22 3H16C14.9391 3 13.9217 3.42143 13.1716 4.17157C12.4214 4.92172 12 5.93913 12 7V21C12 20.2044 12.3161 19.4413 12.8787 18.8787C13.4413 18.3161 14.2044 18 15 18H22V3Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ExerciseIcon = ({ active }: { active: boolean }) => (
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" className={active ? "text-white" : "text-[#5F5C5C]"}>
|
||||||
|
<path
|
||||||
|
d="M6.2 5H12.8C13.9201 5 14.9798 5.44772 15.7549 6.22183C16.5301 6.99594 16.9778 8.05556 16.9778 9.17647V19.8235C16.9778 18.7026 16.5301 17.643 15.7549 16.8689C14.9798 16.0948 13.9201 15.6471 12.8 15.6471H6.2V5Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.2 5C5.07989 5 4.02015 5.44772 3.24505 6.22183C2.46995 6.99594 2.02222 8.05556 2.02222 9.17647V19.8235C2.02222 18.7026 2.46995 17.643 3.24505 16.8689C4.02015 16.0948 5.07989 15.6471 6.2 15.6471"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
const SettingsIcon = ({ active }: { active: boolean }) => (
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" className={active ? "text-white" : "text-[#5F5C5C]"}>
|
||||||
|
<circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="2" />
|
||||||
|
<path
|
||||||
|
d="M19.4 15C19.2669 15.3016 19.2272 15.6362 19.286 15.9606C19.3448 16.285 19.4995 16.5843 19.73 16.82L19.79 16.88C19.976 17.0657 20.1235 17.2863 20.2241 17.5291C20.3248 17.7719 20.3766 18.0322 20.3766 18.295C20.3766 18.5578 20.3248 18.8181 20.2241 19.0609C20.1235 19.3037 19.976 19.5243 19.79 19.71C19.6043 19.896 19.3837 20.0435 19.1409 20.1441C18.8981 20.2448 18.6378 20.2966 18.375 20.2966C18.1122 20.2966 17.8519 20.2448 17.6091 20.1441C17.3663 20.0435 17.1457 19.896 16.96 19.71L16.9 19.65C16.6643 19.4195 16.365 19.2648 16.0406 19.206C15.7162 19.1472 15.3816 19.1869 15.08 19.32C14.7842 19.4468 14.532 19.6572 14.3543 19.9255C14.1766 20.1938 14.0813 20.5082 14.08 20.83V21C14.08 21.5304 13.8693 22.0391 13.4942 22.4142C13.1191 22.7893 12.6104 23 12.08 23C11.5496 23 11.0409 22.7893 10.6658 22.4142C10.2907 22.0391 10.08 21.5304 10.08 21V20.91C10.0723 20.579 9.96512 20.2569 9.77251 19.9859C9.5799 19.7148 9.31074 19.5063 9 19.38C8.69838 19.2469 8.36381 19.2072 8.03941 19.266C7.71502 19.3248 7.41568 19.4795 7.18 19.71L7.12 19.77C6.93425 19.956 6.71368 20.1035 6.47088 20.2041C6.22808 20.3048 5.96783 20.3566 5.705 20.3566C5.44217 20.3566 5.18192 20.3048 4.93912 20.2041C4.69632 20.1035 4.47575 19.956 4.29 19.77C4.10405 19.5843 3.95653 19.3637 3.85588 19.1209C3.75523 18.8781 3.70343 18.6178 3.70343 18.355C3.70343 18.0922 3.75523 17.8319 3.85588 17.5891C3.95653 17.3463 4.10405 17.1257 4.29 16.94L4.35 16.88C4.58054 16.6443 4.73519 16.345 4.794 16.0206C4.85282 15.6962 4.81312 15.3616 4.68 15.06C4.55324 14.7642 4.34276 14.512 4.07447 14.3343C3.80618 14.1566 3.49179 14.0613 3.17 14.06H3C2.46957 14.06 1.96086 13.8493 1.58579 13.4742C1.21071 13.0991 1 12.5904 1 12.06C1 11.5296 1.21071 11.0209 1.58579 10.6458C1.96086 10.2707 2.46957 10.06 3 10.06H3.09C3.42099 10.0523 3.742 9.94512 4.01309 9.75251C4.28417 9.5599 4.49268 9.29074 4.62 8.98C4.75312 8.67838 4.79282 8.34381 4.734 8.01941C4.67519 7.69502 4.52054 7.39568 4.29 7.16L4.23 7.1C4.04405 6.91425 3.89653 6.69368 3.79588 6.45088C3.69523 6.20808 3.64343 5.94783 3.64343 5.685C3.64343 5.42217 3.69523 5.16192 3.79588 4.91912C3.89653 4.67632 4.04405 4.45575 4.23 4.27C4.41575 4.08405 4.63632 3.93653 4.87912 3.83588C5.12192 3.73523 5.38217 3.68343 5.645 3.68343C5.90783 3.68343 6.16808 3.73523 6.41088 3.83588C6.65368 3.93653 6.87425 4.08405 7.06 4.27L7.12 4.33C7.35568 4.56054 7.65502 4.71519 7.97941 4.774C8.30381 4.83282 8.63838 4.79312 8.94 4.66H9C9.29577 4.53324 9.54802 4.32276 9.72569 4.05447C9.90337 3.78618 9.99872 3.47179 10 3.15V3C10 2.46957 10.2107 1.96086 10.5858 1.58579C10.9609 1.21071 11.4696 1 12 1C12.5304 1 13.0391 1.21071 13.4142 1.58579C13.7893 1.96086 14 2.46957 14 3V3.09C14.0013 3.41179 14.0966 3.72618 14.2743 3.99447C14.452 4.26276 14.7042 4.47324 15 4.6C15.3016 4.73312 15.6362 4.77282 15.9606 4.714C16.285 4.65519 16.5843 4.50054 16.82 4.27L16.88 4.21C17.0657 4.02405 17.2863 3.87653 17.5291 3.77588C17.7719 3.67523 18.0322 3.62343 18.295 3.62343C18.5578 3.62343 18.8181 3.67523 19.0609 3.77588C19.3037 3.87653 19.5243 4.02405 19.71 4.21C19.896 4.39575 20.0435 4.61632 20.1441 4.85912C20.2448 5.10192 20.2966 5.36217 20.2966 5.625C20.2966 5.88783 20.2448 6.14808 20.1441 6.39088C20.0435 6.63368 19.896 6.85425 19.71 7.04L19.65 7.1C19.4195 7.33568 19.2648 7.63502 19.206 7.95941C19.1472 8.28381 19.1869 8.61838 19.32 8.92V9C19.4468 9.29577 19.6572 9.54802 19.9255 9.72569C20.1938 9.90337 20.5082 9.99872 20.83 10H21C21.5304 10 22.0391 10.2107 22.4142 10.5858C22.7893 10.9609 23 11.4696 23 12C23 12.5304 22.7893 13.0391 22.4142 13.4142C22.0391 13.7893 21.5304 14 21 14H20.91C20.5882 14.0013 20.2738 14.0966 20.0055 14.2743C19.7372 14.452 19.5268 14.7042 19.4 15Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ path: "/home", icon: HomeIcon, label: "Домой" },
|
||||||
|
{ path: "/courses", icon: CoursesIcon, label: "Курсы" },
|
||||||
|
{ path: "/exercise/1", icon: ExerciseIcon, label: "Заниматься" },
|
||||||
|
{ path: "/settings", icon: SettingsIcon, label: "Меню" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const isActive = (path: string) => {
|
||||||
|
if (path === "/exercise/1") return location.pathname.includes("/exercise")
|
||||||
|
return location.pathname === path
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed bottom-0 left-0 right-0 bg-white/20 backdrop-blur-xl border-t border-white/10 px-4 py-3 z-50 shadow-2xl">
|
||||||
|
<div className="flex justify-around items-center max-w-md mx-auto">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const active = isActive(item.path)
|
||||||
|
const IconComponent = item.icon
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={item.path}
|
||||||
|
onClick={() => history.push(item.path)}
|
||||||
|
className={`flex flex-col items-center py-2 px-4 rounded-2xl transition-all duration-300 transform ${
|
||||||
|
active
|
||||||
|
? "text-white bg-[#2BACBE]/80 backdrop-blur-lg scale-110 shadow-lg"
|
||||||
|
: "text-[#5F5C5C] hover:text-[#2BACBE] hover:bg-white/10 hover:scale-105"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="mb-1 filter drop-shadow-sm">
|
||||||
|
<IconComponent active={active} />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-semibold tracking-wide">{item.label}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BottomNavigation
|
@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
function Footer() {
|
|
||||||
return (
|
|
||||||
<div className='mt-auto bg-fuchsia-400'>
|
|
||||||
<h1>Footer</h1>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Footer;
|
|
@ -1,16 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { IonRouterLink } from '@ionic/react';
|
|
||||||
|
|
||||||
function Header() {
|
|
||||||
return (
|
|
||||||
<div className='fixed top-0 left-0 w-full z-50 bg-fuchsia-400 shadow h-12 px-4 flex justify-between'>
|
|
||||||
<h1>Вмеда</h1>
|
|
||||||
<IonRouterLink href="/register">
|
|
||||||
<a className="text-sm">Зарегистрироваться</a>
|
|
||||||
</IonRouterLink>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
function MainContent() {
|
|
||||||
return (
|
|
||||||
<div className="flex-1 mt-12 bg-green-200">
|
|
||||||
<p>main content</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MainContent;
|
|
||||||
|
|
360
src/index.css
360
src/index.css
@ -1 +1,361 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.145 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.145 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.985 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.396 0.141 25.723);
|
||||||
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||||
|
--border: oklch(0.269 0 0);
|
||||||
|
--input: oklch(0.269 0 0);
|
||||||
|
--ring: oklch(0.439 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(0.269 0 0);
|
||||||
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.glass-morphism {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-morphism-dark {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(43, 172, 190, 0.3) transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.animate-float {
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-up {
|
||||||
|
animation: slideUp 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fadeIn 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-scale-in {
|
||||||
|
animation: scaleIn 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-bounce-gentle {
|
||||||
|
animation: bounceGentle 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-glow {
|
||||||
|
animation: glow 2s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0px) rotate(0deg);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translateY(-10px) rotate(1deg);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translateY(-5px) rotate(-1deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounceGentle {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
from {
|
||||||
|
box-shadow: 0 0 20px rgba(43, 172, 190, 0.3);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
box-shadow: 0 0 30px rgba(43, 172, 190, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Enhanced scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: linear-gradient(45deg, #2bacbe, #5f5c5c);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: linear-gradient(45deg, #2bacbe, #2bacbe);
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth transitions for all interactive elements */
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
div {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for accessibility */
|
||||||
|
button:focus,
|
||||||
|
input:focus,
|
||||||
|
select:focus {
|
||||||
|
outline: 2px solid #2bacbe;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading animation */
|
||||||
|
.loading-spinner {
|
||||||
|
border: 3px solid rgba(43, 172, 190, 0.1);
|
||||||
|
border-top: 3px solid #2bacbe;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Backdrop blur support */
|
||||||
|
@supports (backdrop-filter: blur(20px)) {
|
||||||
|
.backdrop-blur-2xl {
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (backdrop-filter: blur(20px)) {
|
||||||
|
.backdrop-blur-2xl {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern gradient backgrounds */
|
||||||
|
.gradient-mesh {
|
||||||
|
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradientShift 15s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientShift {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced card hover effects */
|
||||||
|
.card-hover {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: translateY(-4px) scale(1.02);
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improved button styles */
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
141
src/pages/CourseComplete.tsx
Normal file
141
src/pages/CourseComplete.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useHistory } from "react-router-dom"
|
||||||
|
|
||||||
|
const CourseComplete: React.FC = () => {
|
||||||
|
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)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer1)
|
||||||
|
clearTimeout(timer2)
|
||||||
|
clearTimeout(timer3)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-green-400 via-blue-500 to-purple-600 flex items-center justify-center p-4 relative overflow-hidden">
|
||||||
|
{/* Background Animation */}
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<div className="absolute top-20 left-10 w-20 h-20 bg-white/10 rounded-full animate-pulse"></div>
|
||||||
|
<div className="absolute bottom-32 right-16 w-16 h-16 bg-white/10 rounded-full animate-bounce"></div>
|
||||||
|
<div className="absolute top-1/2 left-1/4 w-12 h-12 bg-white/10 rounded-full animate-ping"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="text-center z-10 max-w-md">
|
||||||
|
{/* Volleyball Animation */}
|
||||||
|
<div className="relative mb-8 h-40">
|
||||||
|
{/* Net */}
|
||||||
|
<div
|
||||||
|
className={`absolute bottom-0 left-1/2 transform -translate-x-1/2 transition-all duration-1000 ${
|
||||||
|
animationPhase >= 1 ? "opacity-100" : "opacity-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="w-32 h-20 border-2 border-white/60 border-b-0">
|
||||||
|
<div className="grid grid-cols-8 h-full">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<div key={i} className="border-r border-white/40 last:border-r-0"></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-rows-4 h-full absolute inset-0">
|
||||||
|
{Array.from({ length: 4 }).map((_, i) => (
|
||||||
|
<div key={i} className="border-b border-white/40 last:border-b-0"></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Volleyball */}
|
||||||
|
<div
|
||||||
|
className={`absolute transition-all duration-2000 ${
|
||||||
|
animationPhase >= 2
|
||||||
|
? "bottom-8 left-1/2 transform -translate-x-1/2 scale-75"
|
||||||
|
: "bottom-32 left-8 scale-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-16 h-16 rounded-full bg-white flex items-center justify-center text-2xl shadow-lg ${
|
||||||
|
animationPhase >= 2 ? "animate-bounce" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
🏐
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Success particles */}
|
||||||
|
{animationPhase >= 3 && (
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<div className="absolute top-4 left-8 animate-ping">⭐</div>
|
||||||
|
<div className="absolute top-8 right-12 animate-pulse">✨</div>
|
||||||
|
<div className="absolute bottom-16 left-16 animate-bounce">🎉</div>
|
||||||
|
<div className="absolute bottom-12 right-8 animate-ping">🏆</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Congratulations */}
|
||||||
|
<div
|
||||||
|
className={`transition-all duration-1000 delay-500 ${
|
||||||
|
animationPhase >= 2 ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<h1 className="text-4xl font-bold text-white mb-4">Поздравляем! 🎉</h1>
|
||||||
|
<p className="text-white/90 text-lg mb-2">Вы успешно завершили курс</p>
|
||||||
|
<p className="text-white/80 text-xl font-semibold mb-6">"Восстановление колена"</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div
|
||||||
|
className={`bg-white/10 backdrop-blur-lg rounded-2xl p-6 border border-white/20 mb-8 transition-all duration-1000 delay-1000 ${
|
||||||
|
animationPhase >= 3 ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-3 gap-4 text-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-white">12</div>
|
||||||
|
<div className="text-white/70 text-sm">Упражнений</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-white">4</div>
|
||||||
|
<div className="text-white/70 text-sm">Недели</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-white">100%</div>
|
||||||
|
<div className="text-white/70 text-sm">Завершено</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div
|
||||||
|
className={`space-y-4 transition-all duration-1000 delay-1500 ${
|
||||||
|
animationPhase >= 3 ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => history.push("/courses")}
|
||||||
|
className="w-full bg-white/20 backdrop-blur-lg hover:bg-white/30 text-white font-semibold py-3 px-6 rounded-xl border border-white/30 transition-all duration-200 transform hover:scale-105"
|
||||||
|
>
|
||||||
|
Выбрать новый курс
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => history.push("/home")}
|
||||||
|
className="w-full bg-transparent border-2 border-white/50 hover:bg-white/10 text-white font-semibold py-3 px-6 rounded-xl transition-all duration-200"
|
||||||
|
>
|
||||||
|
На главную
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CourseComplete
|
312
src/pages/CourseExercises.tsx
Normal file
312
src/pages/CourseExercises.tsx
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useHistory, useParams } from "react-router-dom"
|
||||||
|
import BottomNavigation from "../components/BottomNavigation"
|
||||||
|
|
||||||
|
const CourseExercises: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
const { id } = useParams<{ id: string }>()
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0)
|
||||||
|
|
||||||
|
const exercises = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Подъемы ног лежа",
|
||||||
|
duration: "15 мин",
|
||||||
|
sets: 3,
|
||||||
|
reps: 12,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Легкий",
|
||||||
|
description: "Укрепление мышц бедра и улучшение подвижности коленного сустава",
|
||||||
|
calories: 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Приседания у стены",
|
||||||
|
duration: "10 мин",
|
||||||
|
sets: 2,
|
||||||
|
reps: 15,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Средний",
|
||||||
|
description: "Безопасные приседания для восстановления силы ног",
|
||||||
|
calories: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Растяжка квадрицепса",
|
||||||
|
duration: "8 мин",
|
||||||
|
sets: 1,
|
||||||
|
reps: 30,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Легкий",
|
||||||
|
description: "Улучшение гибкости и снятие напряжения",
|
||||||
|
calories: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Укрепление икр",
|
||||||
|
duration: "12 мин",
|
||||||
|
sets: 3,
|
||||||
|
reps: 20,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Средний",
|
||||||
|
description: "Развитие силы и выносливости икроножных мышц",
|
||||||
|
calories: 40,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const nextSlide = () => {
|
||||||
|
setCurrentSlide((prev) => (prev + 1) % exercises.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevSlide = () => {
|
||||||
|
setCurrentSlide((prev) => (prev - 1 + exercises.length) % exercises.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentExercise = exercises[currentSlide]
|
||||||
|
|
||||||
|
const getDifficultyColor = (difficulty: string) => {
|
||||||
|
switch (difficulty) {
|
||||||
|
case "Легкий":
|
||||||
|
return "bg-gradient-to-r from-emerald-400 to-green-500"
|
||||||
|
case "Средний":
|
||||||
|
return "bg-gradient-to-r from-amber-400 to-orange-500"
|
||||||
|
case "Сложный":
|
||||||
|
return "bg-gradient-to-r from-red-400 to-pink-500"
|
||||||
|
default:
|
||||||
|
return "bg-gradient-to-r from-gray-400 to-gray-500"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 overflow-y-auto scrollable-content">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="sticky top-0 z-40 bg-gradient-to-br from-[#2BACBE] via-blue-500 to-indigo-600 backdrop-blur-xl">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/10 to-transparent"></div>
|
||||||
|
<div className="relative px-4 sm:px-6 pt-12 pb-6">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<button
|
||||||
|
onClick={() => history.goBack()}
|
||||||
|
className="w-12 h-12 bg-white/20 backdrop-blur-xl rounded-2xl flex items-center justify-center border border-white/30 hover:bg-white/30 transition-all duration-300 shadow-lg"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-xl sm:text-2xl font-black text-white">Восстановление колена</h1>
|
||||||
|
<p className="text-blue-100 text-sm font-medium">Курс упражнений</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-white/20 backdrop-blur-xl rounded-2xl flex items-center justify-center border border-white/30 shadow-lg">
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Exercise Slider */}
|
||||||
|
<div className="px-4 sm:px-6 -mt-4 mb-8">
|
||||||
|
<div className="glass-morphism rounded-3xl border border-white/50 shadow-2xl overflow-hidden backdrop-blur-2xl">
|
||||||
|
{/* Exercise Image */}
|
||||||
|
<div className="relative">
|
||||||
|
<img
|
||||||
|
src={currentExercise.image || "/placeholder.svg"}
|
||||||
|
alt={currentExercise.name}
|
||||||
|
className="w-full h-56 sm:h-64 object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-black/10"></div>
|
||||||
|
|
||||||
|
{/* Difficulty Badge */}
|
||||||
|
<div className="absolute top-4 right-4">
|
||||||
|
<div
|
||||||
|
className={`px-4 py-2 rounded-full text-xs font-bold text-white shadow-lg backdrop-blur-sm ${getDifficultyColor(currentExercise.difficulty)}`}
|
||||||
|
>
|
||||||
|
{currentExercise.difficulty}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation arrows */}
|
||||||
|
<button
|
||||||
|
onClick={prevSlide}
|
||||||
|
className="absolute left-4 top-1/2 transform -translate-y-1/2 w-12 h-12 bg-black/20 backdrop-blur-xl rounded-full flex items-center justify-center text-white hover:bg-black/30 transition-all duration-300 shadow-lg border border-white/20"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={nextSlide}
|
||||||
|
className="absolute right-4 top-1/2 transform -translate-y-1/2 w-12 h-12 bg-black/20 backdrop-blur-xl rounded-full flex items-center justify-center text-white hover:bg-black/30 transition-all duration-300 shadow-lg border border-white/20"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Play Button Overlay */}
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-20 h-20 bg-white/90 backdrop-blur-xl rounded-full flex items-center justify-center shadow-2xl hover:bg-white transition-all duration-300 transform hover:scale-110 border border-white/50">
|
||||||
|
<svg className="w-8 h-8 text-gray-800 ml-1" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M8 5v14l11-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Exercise Info */}
|
||||||
|
<div className="p-6 sm:p-8">
|
||||||
|
<h3 className="text-2xl sm:text-3xl font-black text-gray-800 mb-3">{currentExercise.name}</h3>
|
||||||
|
<p className="text-gray-600 mb-6 leading-relaxed text-sm sm:text-base">{currentExercise.description}</p>
|
||||||
|
|
||||||
|
{/* Stats Grid */}
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-r from-blue-400 to-cyan-500 rounded-2xl flex items-center justify-center mx-auto mb-2 shadow-lg">
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="text-lg font-black text-gray-800">{currentExercise.duration}</div>
|
||||||
|
<div className="text-xs text-gray-600 font-medium">Время</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-r from-green-400 to-emerald-500 rounded-2xl flex items-center justify-center mx-auto mb-2 shadow-lg">
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="text-lg font-black text-gray-800">{currentExercise.sets}</div>
|
||||||
|
<div className="text-xs text-gray-600 font-medium">Подходы</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-r from-purple-400 to-pink-500 rounded-2xl flex items-center justify-center mx-auto mb-2 shadow-lg">
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="text-lg font-black text-gray-800">{currentExercise.reps}</div>
|
||||||
|
<div className="text-xs text-gray-600 font-medium">Повторы</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-r from-orange-400 to-red-500 rounded-2xl flex items-center justify-center mx-auto mb-2 shadow-lg">
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="text-lg font-black text-gray-800">{currentExercise.calories}</div>
|
||||||
|
<div className="text-xs text-gray-600 font-medium">Калории</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => history.push(`/exercise/${currentExercise.id}`)}
|
||||||
|
className="w-full bg-gradient-to-r from-[#2BACBE] via-blue-500 to-indigo-600 text-white font-black py-4 px-6 rounded-2xl hover:shadow-2xl transition-all duration-300 transform hover:scale-105 shadow-lg backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
🚀 Начать упражнение
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Slide indicators */}
|
||||||
|
<div className="flex justify-center mt-6 space-x-2">
|
||||||
|
{exercises.map((_, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => setCurrentSlide(index)}
|
||||||
|
className={`h-2 rounded-full transition-all duration-300 ${
|
||||||
|
index === currentSlide
|
||||||
|
? "bg-gradient-to-r from-[#2BACBE] to-blue-600 w-8 shadow-lg"
|
||||||
|
: "bg-gray-300 w-2 hover:bg-gray-400"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Exercise List */}
|
||||||
|
<div className="px-4 sm:px-6 pb-28">
|
||||||
|
<h2 className="text-xl sm:text-2xl font-black text-gray-800 mb-6">Все упражнения курса</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{exercises.map((exercise, index) => (
|
||||||
|
<div
|
||||||
|
key={exercise.id}
|
||||||
|
onClick={() => history.push(`/exercise/${exercise.id}`)}
|
||||||
|
className={`glass-morphism rounded-2xl p-4 sm:p-6 border border-white/50 shadow-lg cursor-pointer transition-all duration-300 hover:shadow-2xl transform hover:scale-[1.02] backdrop-blur-xl ${
|
||||||
|
index === currentSlide ? "ring-2 ring-[#2BACBE] bg-blue-50/20" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-14 h-14 sm:w-16 sm:h-16 bg-gradient-to-br from-[#2BACBE] via-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center text-white font-black text-lg sm:text-xl shadow-xl">
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
<div className="absolute -top-1 -right-1 w-6 h-6 bg-white rounded-full flex items-center justify-center shadow-lg border border-gray-200">
|
||||||
|
<span className="text-xs">{index < currentSlide ? "✅" : index === currentSlide ? "▶️" : "⏳"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h3 className="font-black text-gray-800 text-lg sm:text-xl truncate">{exercise.name}</h3>
|
||||||
|
<p className="text-gray-600 text-sm mb-2 line-clamp-2">{exercise.description}</p>
|
||||||
|
<div className="flex items-center space-x-4 text-xs text-gray-500">
|
||||||
|
<span className="flex items-center space-x-1">
|
||||||
|
<span>💪</span>
|
||||||
|
<span>
|
||||||
|
{exercise.sets} × {exercise.reps}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center space-x-1">
|
||||||
|
<span>⏱️</span>
|
||||||
|
<span>{exercise.duration}</span>
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center space-x-1">
|
||||||
|
<span>🔥</span>
|
||||||
|
<span>{exercise.calories} кал</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-[#2BACBE] transform transition-transform duration-300 hover:translate-x-1">
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNavigation />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CourseExercises
|
130
src/pages/Courses.tsx
Normal file
130
src/pages/Courses.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import BottomNavigation from "../components/BottomNavigation"
|
||||||
|
import { useHistory } from "react-router-dom"
|
||||||
|
|
||||||
|
const Home: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
const currentDate = new Date().toLocaleDateString("ru-RU", {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
|
||||||
|
const courses = [
|
||||||
|
{ id: 1, name: "Восстановление колена", progress: 75, color: "from-[#2BACBE] to-[#2BACBE]/80" },
|
||||||
|
{ id: 2, name: "Укрепление спины", progress: 45, color: "from-[#5F5C5C] to-[#5F5C5C]/80" },
|
||||||
|
{ id: 3, name: "Реабилитация плеча", progress: 90, color: "from-[#2BACBE]/80 to-[#5F5C5C]" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 pb-24 overflow-y-auto">
|
||||||
|
{/* Welcome Section */}
|
||||||
|
<div className="pt-12 px-6 mb-8">
|
||||||
|
<div className="bg-white/40 backdrop-blur-2xl rounded-3xl p-6 border border-white/20 shadow-2xl">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-black text-[#5F5C5C] mb-1">Привет, Александр!</h1>
|
||||||
|
<p className="text-[#5F5C5C]/70 font-medium">{currentDate}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-16 h-16 bg-gradient-to-br from-[#2BACBE] to-[#2BACBE]/80 backdrop-blur-xl rounded-2xl flex items-center justify-center border border-white/30 shadow-lg">
|
||||||
|
<span className="text-2xl filter drop-shadow-sm">👤</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Analytics Cards */}
|
||||||
|
<div className="px-6 mb-8">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="bg-white/30 backdrop-blur-2xl rounded-2xl p-5 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-3xl font-black text-[#2BACBE] mb-1">12</div>
|
||||||
|
<div className="text-sm text-[#5F5C5C] font-semibold">Дней подряд</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white/30 backdrop-blur-2xl rounded-2xl p-5 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-3xl font-black text-[#2BACBE] mb-1">85%</div>
|
||||||
|
<div className="text-sm text-[#5F5C5C] font-semibold">Прогресс</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Current Exercise */}
|
||||||
|
<div className="px-6 mb-8">
|
||||||
|
<h2 className="text-xl font-black text-[#5F5C5C] mb-4">Текущее упражнение</h2>
|
||||||
|
<div className="bg-white/30 backdrop-blur-2xl rounded-3xl p-6 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300">
|
||||||
|
<div className="flex items-center space-x-5">
|
||||||
|
<div className="w-20 h-20 bg-gradient-to-br from-[#2BACBE] to-[#2BACBE]/80 rounded-2xl flex items-center justify-center shadow-lg">
|
||||||
|
<span className="text-3xl filter drop-shadow-sm">🦵</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-black text-[#5F5C5C] text-lg mb-1">Подъемы ног</h3>
|
||||||
|
<p className="text-sm text-[#5F5C5C]/70 font-medium mb-3">Восстановление колена • 3 подхода</p>
|
||||||
|
<div className="bg-white/50 rounded-full h-3 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="bg-gradient-to-r from-[#2BACBE] to-[#2BACBE]/80 h-3 rounded-full shadow-sm transition-all duration-500"
|
||||||
|
style={{ width: "60%" }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => history.push("/exercise/1")}
|
||||||
|
className="bg-gradient-to-r from-[#2BACBE] to-[#2BACBE]/80 text-white px-6 py-3 rounded-2xl font-bold hover:shadow-xl transition-all duration-300 transform hover:scale-105 backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
Продолжить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Courses */}
|
||||||
|
<div className="px-6 mb-8">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h2 className="text-xl font-black text-[#5F5C5C]">Мои курсы</h2>
|
||||||
|
<button onClick={() => history.push("/courses")} className="text-[#2BACBE] text-sm font-bold hover:underline">
|
||||||
|
Все курсы
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{courses.map((course) => (
|
||||||
|
<div
|
||||||
|
key={course.id}
|
||||||
|
onClick={() => history.push(`/course/${course.id}/exercises`)}
|
||||||
|
className="bg-white/30 backdrop-blur-2xl rounded-3xl p-6 border border-white/20 shadow-xl cursor-pointer hover:shadow-2xl transition-all duration-300 transform hover:scale-[1.02]"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-5">
|
||||||
|
<div
|
||||||
|
className={`w-16 h-16 bg-gradient-to-br ${course.color} rounded-2xl flex items-center justify-center shadow-lg`}
|
||||||
|
>
|
||||||
|
<span className="text-2xl text-white filter drop-shadow-sm">💪</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-black text-[#5F5C5C] text-lg mb-2">{course.name}</h3>
|
||||||
|
<div className="bg-white/50 rounded-full h-3 mb-2 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className={`bg-gradient-to-r ${course.color} h-3 rounded-full transition-all duration-700 shadow-sm`}
|
||||||
|
style={{ width: `${course.progress}%` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-[#5F5C5C]/70 font-semibold">{course.progress}% завершено</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-[#2BACBE] transform transition-transform duration-300 hover:translate-x-1">
|
||||||
|
<span className="text-2xl font-bold">→</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNavigation />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home
|
264
src/pages/Exercise.tsx
Normal file
264
src/pages/Exercise.tsx
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { useHistory, useParams } from "react-router-dom"
|
||||||
|
import BottomNavigation from "../components/BottomNavigation"
|
||||||
|
|
||||||
|
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
|
||||||
|
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 () => 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 exerciseSteps = [
|
||||||
|
{
|
||||||
|
title: "Исходное положение",
|
||||||
|
description:
|
||||||
|
"Лягте на спину на коврик. Руки вдоль тела, ладони прижаты к полу. Ноги выпрямлены, носки направлены вверх. Поясница плотно прижата к полу.",
|
||||||
|
icon: "1️⃣",
|
||||||
|
color: "from-blue-400 to-cyan-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Задание",
|
||||||
|
description:
|
||||||
|
"Медленно поднимите прямые ноги до угла 90 градусов. Задержитесь на 2 секунды, затем медленно опустите ноги, не касаясь пола. Повторите движение плавно и контролируемо.",
|
||||||
|
icon: "2️⃣",
|
||||||
|
color: "from-emerald-400 to-green-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Подходы",
|
||||||
|
description: "Выполните 3 подхода по 12 повторений с отдыхом 60 секунд между подходами.",
|
||||||
|
icon: "3️⃣",
|
||||||
|
color: "from-purple-400 to-pink-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Перерыв",
|
||||||
|
description: "Отдыхайте 60 секунд между подходами. Дышите спокойно и расслабьте мышцы.",
|
||||||
|
icon: "4️⃣",
|
||||||
|
color: "from-orange-400 to-red-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Динамика и статика",
|
||||||
|
description:
|
||||||
|
"Динамическая фаза: подъем и опускание ног выполняется плавно, 2 секунды вверх, 2 секунды вниз. Статическая фаза: удержание ног в верхней точке на 2 секунды.",
|
||||||
|
icon: "5️⃣",
|
||||||
|
color: "from-indigo-400 to-purple-500",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 overflow-y-auto scrollable-content">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="sticky top-0 z-40 bg-gradient-to-br from-[#2BACBE] via-blue-500 to-indigo-600 backdrop-blur-xl">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/10 to-transparent"></div>
|
||||||
|
<div className="relative px-4 sm:px-6 pt-12 pb-4">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<button
|
||||||
|
onClick={() => history.goBack()}
|
||||||
|
className="w-12 h-12 bg-white/20 backdrop-blur-xl rounded-2xl flex items-center justify-center border border-white/30 hover:bg-white/30 transition-all duration-300 shadow-lg"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-lg sm:text-xl font-black text-white">Подъемы ног лежа</h1>
|
||||||
|
<p className="text-blue-100 text-sm font-medium">Упражнение 1 из 12</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-white/20 backdrop-blur-xl rounded-2xl flex items-center justify-center border border-white/30 shadow-lg">
|
||||||
|
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Set Counter */}
|
||||||
|
<div className="flex justify-center space-x-2">
|
||||||
|
{Array.from({ length: totalSets }).map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-all duration-300 ${
|
||||||
|
index + 1 <= currentSet
|
||||||
|
? "bg-white text-[#2BACBE] shadow-lg"
|
||||||
|
: "bg-white/20 text-white border border-white/30"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Video/Image Section */}
|
||||||
|
<div className="px-4 sm:px-6 -mt-4 mb-6">
|
||||||
|
<div className="glass-morphism rounded-3xl overflow-hidden shadow-2xl border border-white/50 backdrop-blur-2xl">
|
||||||
|
<div className="relative">
|
||||||
|
<img
|
||||||
|
src="/placeholder.svg?height=250&width=400"
|
||||||
|
alt="Exercise demonstration"
|
||||||
|
className="w-full h-56 sm:h-64 object-cover"
|
||||||
|
/>
|
||||||
|
<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="w-20 h-20 bg-white/90 backdrop-blur-xl rounded-full flex items-center justify-center shadow-2xl hover:bg-white transition-all duration-300 transform hover:scale-110 border border-white/50"
|
||||||
|
>
|
||||||
|
<span className="text-3xl text-gray-800 ml-1">{isPlaying ? "⏸️" : "▶️"}</span>
|
||||||
|
</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-red-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">
|
||||||
|
LIVE
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Timer Display */}
|
||||||
|
<div className="absolute top-4 right-4 bg-black/30 backdrop-blur-sm px-3 py-1 rounded-full">
|
||||||
|
<span className="text-white text-sm font-bold">{formatTime(currentTime)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Exercise Steps */}
|
||||||
|
<div className="px-4 sm:px-6 space-y-4 mb-6">
|
||||||
|
{exerciseSteps.map((step, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="glass-morphism rounded-2xl p-5 border border-white/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-xl"
|
||||||
|
>
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
<div
|
||||||
|
className={`w-12 h-12 bg-gradient-to-r ${step.color} rounded-xl flex items-center justify-center shadow-lg flex-shrink-0`}
|
||||||
|
>
|
||||||
|
<span className="text-lg">{step.icon}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-black text-gray-800 mb-2">{step.title}</h3>
|
||||||
|
<p className="text-gray-700 leading-relaxed text-sm sm:text-base">{step.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Important Notes */}
|
||||||
|
<div className="px-4 sm:px-6 mb-32">
|
||||||
|
<div className="bg-gradient-to-r from-amber-50 to-orange-50 backdrop-blur-xl rounded-2xl p-5 border border-amber-200/50 shadow-lg">
|
||||||
|
<div className="flex items-start space-x-3">
|
||||||
|
<div className="w-8 h-8 bg-gradient-to-r from-amber-400 to-orange-500 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span className="text-white font-bold text-sm">!</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-black text-amber-800 mb-2">Важные замечания</h3>
|
||||||
|
<ul className="space-y-1 text-amber-800 text-sm">
|
||||||
|
<li>• Не отрывайте поясницу от пола</li>
|
||||||
|
<li>• Дышите равномерно, не задерживайте дыхание</li>
|
||||||
|
<li>• При болевых ощущениях немедленно прекратите</li>
|
||||||
|
<li>• Движения должны быть плавными и контролируемыми</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fixed Timer at Bottom */}
|
||||||
|
<div className="fixed bottom-20 left-0 right-0 bg-white/95 backdrop-blur-2xl border-t border-gray-200/50 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-600">
|
||||||
|
Подход {currentSet} из {totalSets}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-black text-gray-800">
|
||||||
|
{formatTime(currentTime)} / {formatTime(totalTime)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gray-200 rounded-full h-3 mb-4 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="bg-gradient-to-r from-[#2BACBE] via-blue-500 to-indigo-600 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 ${
|
||||||
|
isPlaying
|
||||||
|
? "bg-gradient-to-r from-red-500 to-pink-500 hover:from-red-600 hover:to-pink-600 text-white shadow-lg"
|
||||||
|
: "bg-gradient-to-r from-[#2BACBE] via-blue-500 to-indigo-600 hover:shadow-xl text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isPlaying ? "⏸️ Пауза" : "▶️ Начать"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentTime(0)
|
||||||
|
setIsPlaying(false)
|
||||||
|
}}
|
||||||
|
className="px-6 py-3 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold rounded-xl transition-all duration-300 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
🔄
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentSet((prev) => Math.min(prev + 1, totalSets))}
|
||||||
|
className="px-6 py-3 bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-600 hover:to-green-700 text-white font-bold rounded-xl transition-all duration-300 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
✅
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNavigation />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Exercise
|
125
src/pages/ForgotPassword.tsx
Normal file
125
src/pages/ForgotPassword.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useHistory } from "react-router-dom"
|
||||||
|
|
||||||
|
const ForgotPassword: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
const [email, setEmail] = useState("")
|
||||||
|
const [isSubmitted, setIsSubmitted] = useState(false)
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setIsSubmitted(true)
|
||||||
|
// Simulate sending reset email
|
||||||
|
setTimeout(() => {
|
||||||
|
history.push("/login")
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-[#3ABBC7] to-[#0D212C] flex items-center justify-center p-4">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<button
|
||||||
|
onClick={() => history.goBack()}
|
||||||
|
className="absolute top-6 left-6 w-12 h-12 bg-white/10 backdrop-blur-lg rounded-xl flex items-center justify-center border border-white/20 text-white hover:bg-white/20 transition-all"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="w-20 h-20 bg-white/10 backdrop-blur-2xl rounded-2xl flex items-center justify-center mx-auto mb-4 border border-white/20 shadow-2xl">
|
||||||
|
<svg className="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-black text-white mb-2">Забыли пароль?</h1>
|
||||||
|
<p className="text-white/80 font-medium">
|
||||||
|
{isSubmitted ? "Проверьте вашу почту" : "Введите email для восстановления пароля"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<div className="glass-morphism rounded-3xl p-8 border border-white/20 shadow-2xl">
|
||||||
|
{!isSubmitted ? (
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
{/* Email Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Электронная почта</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="example@mail.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
className="absolute right-3 top-3.5 w-5 h-5 text-white/60"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-white/20 backdrop-blur-lg hover:bg-white/30 text-white font-bold py-3 px-6 rounded-2xl border border-white/30 transition-all duration-300 transform hover:scale-105 shadow-lg"
|
||||||
|
>
|
||||||
|
Отправить ссылку
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<div className="text-center space-y-6">
|
||||||
|
{/* Success Icon */}
|
||||||
|
<div className="w-16 h-16 bg-green-500/20 backdrop-blur-lg rounded-full flex items-center justify-center mx-auto border border-green-400/30">
|
||||||
|
<svg className="w-8 h-8 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">Письмо отправлено!</h3>
|
||||||
|
<p className="text-white/80 text-sm leading-relaxed">
|
||||||
|
Мы отправили инструкции по восстановлению пароля на адрес{" "}
|
||||||
|
<span className="font-semibold">{email}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-white/60 text-xs mb-4">Автоматический переход через 3 секунды...</p>
|
||||||
|
<button
|
||||||
|
onClick={() => history.push("/login")}
|
||||||
|
className="text-white/80 text-sm font-medium hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Вернуться к входу
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPassword
|
@ -1,10 +1,236 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
function Home() {
|
import type React from "react"
|
||||||
return (
|
import { useEffect, useState } from "react"
|
||||||
<>
|
import { useHistory } from "react-router-dom"
|
||||||
<p className="pt-20">Домашняя страница</p>
|
import BottomNavigation from "../components/BottomNavigation"
|
||||||
</>
|
|
||||||
);
|
const Home: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
const [currentDate, setCurrentDate] = useState("")
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentDate(
|
||||||
|
new Date().toLocaleDateString("ru-RU", {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ value: "12", label: "Дней подряд", icon: "🔥", color: "from-orange-400 to-red-500" },
|
||||||
|
{ value: "85%", label: "Прогресс", icon: "📈", color: "from-emerald-400 to-green-500" },
|
||||||
|
{ value: "3", label: "Курса", icon: "📚", color: "from-blue-400 to-cyan-500" },
|
||||||
|
{ value: "45", label: "Минут", icon: "⏱️", color: "from-purple-400 to-pink-500" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const courses = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Восстановление колена",
|
||||||
|
progress: 75,
|
||||||
|
color: "from-[#2BACBE] to-blue-600",
|
||||||
|
icon: "🦵",
|
||||||
|
exercises: 12,
|
||||||
|
nextExercise: "Подъемы ног лежа",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Укрепление спины",
|
||||||
|
progress: 45,
|
||||||
|
color: "from-emerald-500 to-green-600",
|
||||||
|
icon: "🏃♂️",
|
||||||
|
exercises: 8,
|
||||||
|
nextExercise: "Планка",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Реабилитация плеча",
|
||||||
|
progress: 90,
|
||||||
|
color: "from-purple-500 to-pink-600",
|
||||||
|
icon: "💪",
|
||||||
|
exercises: 10,
|
||||||
|
nextExercise: "Вращения плечами",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 overflow-y-auto scrollable-content">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-gradient-to-br from-[#3ABBC7] to-[#0D212C] pt-12 pb-8 px-4 sm:px-6 relative overflow-hidden">
|
||||||
|
{/* Background Pattern */}
|
||||||
|
<div className="absolute inset-0 opacity-10">
|
||||||
|
<div className="absolute top-10 left-10 w-20 h-20 bg-white rounded-full animate-pulse"></div>
|
||||||
|
<div className="absolute bottom-10 right-10 w-16 h-16 bg-white rounded-full animate-bounce"></div>
|
||||||
|
<div className="absolute top-1/2 right-1/4 w-12 h-12 bg-white rounded-full animate-ping"></div>
|
||||||
|
<div className="absolute top-1/4 left-1/3 w-8 h-8 bg-white rounded-full animate-pulse"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl sm:text-3xl font-black text-white mb-2">Привет, Александр! 👋</h1>
|
||||||
|
<p className="text-white/80 font-medium text-sm sm:text-base">{currentDate}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-16 h-16 bg-white/20 backdrop-blur-xl rounded-2xl flex items-center justify-center border border-white/30 shadow-xl relative">
|
||||||
|
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div className="absolute -top-1 -right-1 w-5 h-5 bg-green-400 rounded-full border-2 border-white animate-pulse"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats Grid */}
|
||||||
|
<div className="grid grid-cols-2 gap-3 sm:gap-4">
|
||||||
|
{stats.map((stat, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="bg-white/15 backdrop-blur-xl rounded-2xl p-4 border border-white/20 shadow-lg hover:bg-white/20 transition-all duration-300 transform hover:scale-105"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div
|
||||||
|
className={`w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-r ${stat.color} rounded-xl flex items-center justify-center shadow-lg`}
|
||||||
|
>
|
||||||
|
<span className="text-lg sm:text-xl">{stat.icon}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xl sm:text-2xl font-black text-white">{stat.value}</div>
|
||||||
|
<div className="text-white/70 text-xs sm:text-sm font-medium">{stat.label}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-4 sm:px-6 space-y-8 -mt-4 pb-28">
|
||||||
|
{/* Current Exercise */}
|
||||||
|
<div className="glass-morphism rounded-3xl p-6 border border-white/50 shadow-2xl backdrop-blur-2xl">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h2 className="text-xl sm:text-2xl font-black text-gray-800">Текущее упражнение</h2>
|
||||||
|
<div className="px-3 py-1 bg-gradient-to-r from-green-400 to-emerald-500 rounded-full shadow-lg">
|
||||||
|
<span className="text-white text-sm font-bold">Активно</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-20 h-20 bg-gradient-to-br from-[#2BACBE] via-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center shadow-xl">
|
||||||
|
<span className="text-3xl">🦵</span>
|
||||||
|
</div>
|
||||||
|
<div className="absolute -bottom-1 -right-1 w-6 h-6 bg-gradient-to-r from-orange-400 to-red-500 rounded-full flex items-center justify-center shadow-lg animate-pulse">
|
||||||
|
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M8 5v14l11-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h3 className="font-black text-gray-800 text-lg mb-1">Подъемы ног лежа</h3>
|
||||||
|
<p className="text-gray-600 font-medium mb-3 text-sm">Восстановление колена • 3 подхода по 12</p>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="bg-gray-200 rounded-full h-3 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="bg-gradient-to-r from-[#2BACBE] via-blue-500 to-indigo-600 h-3 rounded-full shadow-sm transition-all duration-700"
|
||||||
|
style={{ width: "60%" }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute right-0 -top-6 text-sm font-bold text-gray-600">60%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => history.push("/exercise/1")}
|
||||||
|
className="bg-gradient-to-r from-[#2BACBE] via-blue-500 to-indigo-600 text-white px-4 sm:px-6 py-3 rounded-2xl font-bold hover:shadow-xl transition-all duration-300 transform hover:scale-105 backdrop-blur-sm text-sm sm:text-base"
|
||||||
|
>
|
||||||
|
Продолжить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Stats */}
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div className="glass-morphism rounded-2xl p-4 text-center border border-white/50 shadow-lg backdrop-blur-xl">
|
||||||
|
<div className="text-2xl font-black text-[#2BACBE] mb-1">24</div>
|
||||||
|
<div className="text-xs text-gray-600 font-semibold">Упражнений</div>
|
||||||
|
</div>
|
||||||
|
<div className="glass-morphism rounded-2xl p-4 text-center border border-white/50 shadow-lg backdrop-blur-xl">
|
||||||
|
<div className="text-2xl font-black text-emerald-500 mb-1">180</div>
|
||||||
|
<div className="text-xs text-gray-600 font-semibold">Минут</div>
|
||||||
|
</div>
|
||||||
|
<div className="glass-morphism rounded-2xl p-4 text-center border border-white/50 shadow-lg backdrop-blur-xl">
|
||||||
|
<div className="text-2xl font-black text-purple-500 mb-1">7</div>
|
||||||
|
<div className="text-xs text-gray-600 font-semibold">Дней</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Courses */}
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="text-xl sm:text-2xl font-black text-gray-800">Мои курсы</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => history.push("/courses")}
|
||||||
|
className="text-[#2BACBE] text-sm font-bold hover:underline flex items-center space-x-1"
|
||||||
|
>
|
||||||
|
<span>Все курсы</span>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{courses.map((course) => (
|
||||||
|
<div
|
||||||
|
key={course.id}
|
||||||
|
onClick={() => history.push(`/course/${course.id}/exercises`)}
|
||||||
|
className="glass-morphism rounded-3xl p-6 border border-white/50 shadow-xl cursor-pointer hover:shadow-2xl transition-all duration-300 transform hover:scale-[1.02] backdrop-blur-2xl"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-5">
|
||||||
|
<div
|
||||||
|
className={`w-16 h-16 bg-gradient-to-br ${course.color} rounded-2xl flex items-center justify-center shadow-xl relative`}
|
||||||
|
>
|
||||||
|
<span className="text-2xl text-white filter drop-shadow-sm">{course.icon}</span>
|
||||||
|
<div className="absolute -top-1 -right-1 w-5 h-5 bg-white rounded-full flex items-center justify-center shadow-lg">
|
||||||
|
<span className="text-xs font-bold text-gray-600">{course.exercises}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h3 className="font-black text-gray-800 text-lg mb-1">{course.name}</h3>
|
||||||
|
<p className="text-gray-600 text-sm mb-2">Следующее: {course.nextExercise}</p>
|
||||||
|
<div className="bg-gray-200 rounded-full h-3 mb-2 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className={`bg-gradient-to-r ${course.color} h-3 rounded-full transition-all duration-700 shadow-sm`}
|
||||||
|
style={{ width: `${course.progress}%` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600 font-semibold">{course.progress}% завершено</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-[#2BACBE] transform transition-transform duration-300 hover:translate-x-1">
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNavigation />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home;
|
export default Home
|
||||||
|
@ -1,11 +1,151 @@
|
|||||||
function Login() {
|
"use client"
|
||||||
return (
|
|
||||||
<div className=''>
|
|
||||||
<h1>Войти/залогиниться</h1>
|
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useHistory } from "react-router-dom"
|
||||||
|
|
||||||
|
const Login: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
const [email, setEmail] = useState("")
|
||||||
|
const [password, setPassword] = useState("")
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
|
||||||
|
const handleLogin = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
// Simulate login
|
||||||
|
history.push("/home")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-[#3ABBC7] to-[#0D212C] flex items-center justify-center p-4">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-20 h-20 bg-white/10 backdrop-blur-2xl rounded-2xl flex items-center justify-center mx-auto mb-4 border border-white/20 shadow-2xl">
|
||||||
|
<svg width="40" height="40" viewBox="0 0 100 100" fill="none" className="text-white">
|
||||||
|
<circle cx="50" cy="20" r="8" fill="currentColor" />
|
||||||
|
<path
|
||||||
|
d="M50 28 L50 60 M50 45 L35 35 M50 45 L65 35 M50 60 L35 80 M50 60 L65 80"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-black text-white mb-2">Добро пожаловать!</h1>
|
||||||
|
<p className="text-white/80 font-medium">Войдите в свой аккаунт</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
|
{/* Login Form */}
|
||||||
|
<div className="glass-morphism rounded-3xl p-8 border border-white/20 shadow-2xl">
|
||||||
|
<form onSubmit={handleLogin} className="space-y-6">
|
||||||
|
{/* Email Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Электронная почта</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="example@mail.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
className="absolute right-3 top-3.5 w-5 h-5 text-white/60"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Пароль</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="••••••••"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-3.5 text-white/60 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Forgot Password */}
|
||||||
|
<div className="text-right">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => history.push("/forgot-password")}
|
||||||
|
className="text-white/80 text-sm font-medium hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Забыли пароль?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Login Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-white/20 backdrop-blur-lg hover:bg-white/30 text-white font-bold py-3 px-6 rounded-2xl border border-white/30 transition-all duration-300 transform hover:scale-105 shadow-lg"
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Register Link */}
|
||||||
|
<div className="text-center mt-6">
|
||||||
|
<p className="text-white/80 text-sm">
|
||||||
|
Нет аккаунта?{" "}
|
||||||
|
<button onClick={() => history.push("/register")} className="text-white font-semibold hover:underline">
|
||||||
|
Зарегистрироваться
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Login;
|
export default Login
|
||||||
|
|
||||||
|
@ -1,13 +1,228 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useHistory } from "react-router-dom"
|
||||||
|
|
||||||
|
const Register: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
})
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (formData.password !== formData.confirmPassword) {
|
||||||
|
alert("Пароли не совпадают")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Simulate registration
|
||||||
|
history.push("/home")
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function Auth() {
|
|
||||||
return (
|
return (
|
||||||
<div className='pt-20'>
|
<div className="min-h-screen bg-gradient-to-br from-[#3ABBC7] to-[#0D212C] flex items-center justify-center p-4">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<button
|
||||||
|
onClick={() => history.goBack()}
|
||||||
|
className="absolute top-6 left-6 w-12 h-12 bg-white/10 backdrop-blur-lg rounded-xl flex items-center justify-center border border-white/20 text-white hover:bg-white/20 transition-all"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
<h1 className='cursor-pointer'>Страница регистрации</h1>
|
<div className="w-20 h-20 bg-white/10 backdrop-blur-2xl rounded-2xl flex items-center justify-center mx-auto mb-4 border border-white/20 shadow-2xl">
|
||||||
|
<svg width="40" height="40" viewBox="0 0 100 100" fill="none" className="text-white">
|
||||||
|
<circle cx="50" cy="20" r="8" fill="currentColor" />
|
||||||
|
<path
|
||||||
|
d="M50 28 L50 60 M50 45 L35 35 M50 45 L65 35 M50 60 L35 80 M50 60 L65 80"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-black text-white mb-2">Регистрация</h1>
|
||||||
|
<p className="text-white/80 font-medium">Создайте новый аккаунт</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Registration Form */}
|
||||||
|
<div className="glass-morphism rounded-3xl p-8 border border-white/20 shadow-2xl">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
{/* Name Fields */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Имя</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="firstName"
|
||||||
|
value={formData.firstName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="Иван"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Фамилия</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="lastName"
|
||||||
|
value={formData.lastName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="Иванов"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Email */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Электронная почта</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="example@mail.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Пароль</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
name="password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="••••••••"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-3.5 text-white/60 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Confirm Password */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-white/90 text-sm font-semibold mb-2">Подтвердите пароль</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showConfirmPassword ? "text" : "password"}
|
||||||
|
name="confirmPassword"
|
||||||
|
value={formData.confirmPassword}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="w-full bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl px-4 py-3 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent transition-all"
|
||||||
|
placeholder="••••••••"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||||
|
className="absolute right-3 top-3.5 text-white/60 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{showConfirmPassword ? (
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Register Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-white/20 backdrop-blur-lg hover:bg-white/30 text-white font-bold py-3 px-6 rounded-2xl border border-white/30 transition-all duration-300 transform hover:scale-105 shadow-lg mt-6"
|
||||||
|
>
|
||||||
|
Зарегистрироваться
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Login Link */}
|
||||||
|
<div className="text-center mt-6">
|
||||||
|
<p className="text-white/80 text-sm">
|
||||||
|
Уже есть аккаунт?{" "}
|
||||||
|
<button onClick={() => history.push("/login")} className="text-white font-semibold hover:underline">
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Auth;
|
export default Register
|
||||||
|
204
src/pages/Settings.tsx
Normal file
204
src/pages/Settings.tsx
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useHistory } from "react-router-dom"
|
||||||
|
import BottomNavigation from "../components/BottomNavigation"
|
||||||
|
|
||||||
|
const Settings: React.FC = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
// Simulate logout
|
||||||
|
history.push("/login")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 pb-20">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-gradient-to-r from-gray-600 to-gray-800 px-4 pt-12 pb-6">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-white">Настройки</h1>
|
||||||
|
<p className="text-gray-300">Профиль пациента</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-16 h-16 bg-white/20 backdrop-blur-lg rounded-full flex items-center justify-center border border-white/30">
|
||||||
|
<span className="text-2xl">👤</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Profile Information */}
|
||||||
|
<div className="px-4 -mt-4 mb-6">
|
||||||
|
<div className="bg-white/80 backdrop-blur-lg rounded-2xl p-6 border border-gray-200/50 shadow-lg">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800 mb-4">Личная информация</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Имя</label>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||||
|
<span className="text-gray-800">Александр Петров</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Email</label>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||||
|
<span className="text-gray-800">alexander.petrov@email.com</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Логин</label>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||||
|
<span className="text-gray-800">alex_petrov</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Дата регистрации</label>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||||
|
<span className="text-gray-800">15 марта 2024</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Medical Information */}
|
||||||
|
<div className="px-4 mb-6">
|
||||||
|
<div className="bg-white/80 backdrop-blur-lg rounded-2xl p-6 border border-gray-200/50 shadow-lg">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800 mb-4">Медицинская информация</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Тип травмы</label>
|
||||||
|
<div className="bg-red-50 rounded-lg p-3 border border-red-200">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-red-600">🦵</span>
|
||||||
|
<span className="text-red-800 font-medium">Травма колена</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Описание травмы</label>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||||
|
<p className="text-gray-800 text-sm leading-relaxed">
|
||||||
|
Повреждение передней крестообразной связки левого колена. Получена во время игры в футбол 12 февраля
|
||||||
|
2024 года. Проведена артроскопическая операция 20 февраля 2024 года.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Лечащий врач</label>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||||
|
<span className="text-gray-800">Доктор Иванов И.И.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-600 mb-1">Стадия восстановления</label>
|
||||||
|
<div className="bg-green-50 rounded-lg p-3 border border-green-200">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-green-800 font-medium">Активная реабилитация</span>
|
||||||
|
<span className="text-green-600 text-sm">75% завершено</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 bg-green-200 rounded-full h-2">
|
||||||
|
<div className="bg-green-500 h-2 rounded-full" style={{ width: "75%" }}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Statistics */}
|
||||||
|
<div className="px-4 mb-6">
|
||||||
|
<div className="bg-white/80 backdrop-blur-lg rounded-2xl p-6 border border-gray-200/50 shadow-lg">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800 mb-4">Статистика</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-2xl font-bold text-blue-600">28</div>
|
||||||
|
<div className="text-sm text-gray-600">Дней занятий</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-2xl font-bold text-green-600">156</div>
|
||||||
|
<div className="text-sm text-gray-600">Упражнений выполнено</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-2xl font-bold text-purple-600">3</div>
|
||||||
|
<div className="text-sm text-gray-600">Курса завершено</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-2xl font-bold text-orange-600">42</div>
|
||||||
|
<div className="text-sm text-gray-600">Часов тренировок</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Settings Options */}
|
||||||
|
<div className="px-4 mb-6">
|
||||||
|
<div className="bg-white/80 backdrop-blur-lg rounded-2xl border border-gray-200/50 shadow-lg overflow-hidden">
|
||||||
|
<div className="divide-y divide-gray-200">
|
||||||
|
<button className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<span className="text-xl">🔔</span>
|
||||||
|
<span className="font-medium text-gray-800">Уведомления</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-400">→</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<span className="text-xl">🌙</span>
|
||||||
|
<span className="font-medium text-gray-800">Темная тема</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-400">→</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<span className="text-xl">🌍</span>
|
||||||
|
<span className="font-medium text-gray-800">Язык</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-400">→</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<span className="text-xl">📊</span>
|
||||||
|
<span className="font-medium text-gray-800">Экспорт данных</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-400">→</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logout Button */}
|
||||||
|
<div className="px-4 mb-8">
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-4 px-6 rounded-2xl transition-all duration-200 transform hover:scale-105 shadow-lg"
|
||||||
|
>
|
||||||
|
Выйти из аккаунта
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNavigation />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Settings
|
98
src/pages/Welcome.tsx
Normal file
98
src/pages/Welcome.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useHistory } from "react-router-dom"
|
||||||
|
import manImage from '../assets/man.svg';
|
||||||
|
import emblemImage from '../assets/emblem.png';
|
||||||
|
|
||||||
|
const Welcome: React.FC = () => {
|
||||||
|
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">
|
||||||
|
|
||||||
|
<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}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/* <img
|
||||||
|
className="h-[30rem] lg:h-[0rem] w-auto z-50 absolute right-0 bottom-0 transform opacity-10"
|
||||||
|
src={emblemImage}
|
||||||
|
alt=""
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Welcome
|
14
tailwind.config.js
Normal file
14
tailwind.config.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
const defaultConfig = require("shadcn/ui/tailwind.config")
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...defaultConfig,
|
||||||
|
content: [
|
||||||
|
...defaultConfig.content,
|
||||||
|
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
plugins: [...defaultConfig.plugins, require("tailwindcss-animate")],
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user