mozhem-li-my-sozdat-sayt-gde/index.html

1384 lines
47 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Fitness Progress — Персональный AI-трекер тренировок</title>
<meta name="description" content="Приложение для отслеживания прогресса в зале. Умная прогрессия нагрузок, анализ тренировок, графики и рекорды.">
<style>
* { margin: 0; box-sizing: border-box; }
:root {
--bg: #0a0a0a;
--bg-card: #141414;
--bg-input: #1f1f1f;
--text: #ffffff;
--text-muted: #a0a0a0;
--accent: #00dc82;
--accent-hover: #00ff95;
--danger: #ff4757;
--warning: #ffa502;
--border: #2a2a2a;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
line-height: 1.6;
}
.container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
/* Header */
header {
padding: 20px 0;
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
background: rgba(10, 10, 10, 0.95);
backdrop-filter: blur(10px);
z-index: 100;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo { font-size: 24px; font-weight: 700; color: var(--accent); text-decoration: none; }
.nav { display: flex; gap: 30px; }
.nav a {
color: var(--text-muted);
text-decoration: none;
font-size: 14px;
transition: color 0.2s;
}
.nav a:hover { color: var(--text); }
.cta-btn {
background: var(--accent);
color: #000;
padding: 10px 24px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: background 0.2s;
}
.cta-btn:hover { background: var(--accent-hover); }
/* Hero */
.hero {
padding: 120px 0;
text-align: center;
background: radial-gradient(ellipse at center, rgba(0,220,130,0.1) 0%, transparent 70%);
}
.hero h1 {
font-size: clamp(36px, 6vw, 64px);
margin-bottom: 20px;
background: linear-gradient(135deg, #fff 0%, var(--accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero p {
font-size: clamp(18px, 2vw, 22px);
color: var(--text-muted);
max-width: 600px;
margin: 0 auto 40px;
}
.hero-buttons {
display: flex;
gap: 16px;
justify-content: center;
flex-wrap: wrap;
}
.btn-large {
padding: 16px 32px;
font-size: 18px;
border-radius: 12px;
}
.btn-secondary {
background: var(--bg-input);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover { background: var(--border); }
/* Features */
.features { padding: 100px 0; }
.section-title {
text-align: center;
font-size: clamp(28px, 4vw, 40px);
margin-bottom: 60px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.feature-card {
background: var(--bg-card);
padding: 40px;
border-radius: 16px;
border: 1px solid var(--border);
transition: transform 0.2s, border-color 0.2s;
}
.feature-card:hover {
transform: translateY(-5px);
border-color: var(--accent);
}
.feature-icon {
font-size: 48px;
margin-bottom: 20px;
}
.feature-card h3 {
font-size: 22px;
margin-bottom: 12px;
}
.feature-card p {
color: var(--text-muted);
line-height: 1.7;
}
/* How it works */
.how-it-works {
padding: 100px 0;
background: var(--bg-card);
}
.steps {
display: grid;
gap: 40px;
max-width: 800px;
margin: 0 auto;
}
.step {
display: flex;
gap: 24px;
align-items: flex-start;
}
.step-number {
width: 48px;
height: 48px;
min-width: 48px;
background: var(--accent);
color: #000;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 20px;
}
.step-content h3 { margin-bottom: 8px; }
.step-content p { color: var(--text-muted); }
/* Demo */
.demo { padding: 100px 0; }
.demo-content {
background: var(--bg-card);
border-radius: 16px;
border: 1px solid var(--border);
overflow: hidden;
}
.demo-header {
background: var(--bg-input);
padding: 16px 24px;
border-bottom: 1px solid var(--border);
display: flex;
gap: 8px;
}
.demo-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.demo-dot.red { background: #ff5f56; }
.demo-dot.yellow { background: #ffbd2e; }
.demo-dot.green { background: #27c93f; }
.demo-body {
padding: 40px;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
}
.demo-placeholder {
text-align: center;
color: var(--text-muted);
}
.demo-placeholder .icon {
font-size: 64px;
margin-bottom: 20px;
}
/* Testimonials */
.testimonials {
padding: 100px 0;
background: var(--bg-card);
}
.testimonials-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.testimonial-card {
background: var(--bg);
padding: 30px;
border-radius: 12px;
border: 1px solid var(--border);
}
.testimonial-text {
font-size: 16px;
line-height: 1.7;
margin-bottom: 20px;
font-style: italic;
}
.testimonial-author {
display: flex;
align-items: center;
gap: 12px;
}
.author-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--accent);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: #000;
}
.author-name { font-weight: 600; }
.author-role { color: var(--text-muted); font-size: 14px; }
/* CTA */
.cta {
padding: 120px 0;
text-align: center;
background: radial-gradient(ellipse at center, rgba(0,220,130,0.15) 0%, transparent 70%);
}
.cta h2 {
font-size: clamp(32px, 5vw, 48px);
margin-bottom: 20px;
}
.cta p {
font-size: 18px;
color: var(--text-muted);
max-width: 500px;
margin: 0 auto 40px;
}
/* Footer */
footer {
padding: 60px 0;
border-top: 1px solid var(--border);
text-align: center;
color: var(--text-muted);
}
.footer-links {
display: flex;
justify-content: center;
gap: 30px;
margin-bottom: 30px;
}
.footer-links a {
color: var(--text-muted);
text-decoration: none;
transition: color 0.2s;
}
.footer-links a:hover { color: var(--text); }
/* App (hidden by default) */
.app-container { display: none; }
.app-container.active { display: block; }
.landing-container { display: block; }
.landing-container.hidden { display: none; }
/* App styles */
.app-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid var(--border);
margin-bottom: 30px;
}
.app-nav { display: flex; gap: 20px; }
.app-nav button {
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 8px 16px;
border-radius: 8px;
transition: all 0.2s;
}
.app-nav button.active, .app-nav button:hover {
color: var(--text);
background: var(--bg-input);
}
.auth-box {
max-width: 400px;
margin: 100px auto;
background: var(--bg-card);
padding: 40px;
border-radius: 16px;
border: 1px solid var(--border);
}
.auth-box h2 {
text-align: center;
margin-bottom: 30px;
color: var(--accent);
}
.form-group { margin-bottom: 20px; }
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-muted);
font-size: 14px;
}
.form-group input, .form-group select {
width: 100%;
padding: 12px 16px;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 14px;
}
.form-group input:focus {
outline: none;
border-color: var(--accent);
}
.btn {
width: 100%;
padding: 14px;
background: var(--accent);
color: #000;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
font-size: 16px;
}
.btn:hover { background: var(--accent-hover); }
.auth-switch {
text-align: center;
margin-top: 20px;
color: var(--text-muted);
}
.auth-switch a {
color: var(--accent);
cursor: pointer;
}
/* Dashboard */
.dashboard { display: none; }
.dashboard.active { display: block; }
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--bg-card);
padding: 24px;
border-radius: 12px;
border: 1px solid var(--border);
}
.stat-card .label { color: var(--text-muted); font-size: 13px; margin-bottom: 8px; }
.stat-card .value { font-size: 28px; font-weight: 700; color: var(--accent); }
.section { margin-bottom: 40px; }
.section-title-small {
font-size: 20px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.workout-card {
background: var(--bg-card);
border-radius: 12px;
border: 1px solid var(--border);
padding: 24px;
margin-bottom: 20px;
}
.exercise-item {
background: var(--bg-input);
padding: 16px;
border-radius: 8px;
margin-bottom: 12px;
}
.exercise-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.sets-container { display: grid; gap: 8px; }
.set-row {
display: grid;
grid-template-columns: 40px 1fr 1fr 60px auto;
gap: 8px;
align-items: center;
}
.set-row input {
padding: 8px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
text-align: center;
}
.set-check {
width: 24px;
height: 24px;
accent-color: var(--accent);
cursor: pointer;
}
.btn-small {
padding: 8px 16px;
font-size: 14px;
width: auto;
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.8);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.on { display: flex; }
.modal-content {
background: var(--bg-card);
border-radius: 16px;
padding: 30px;
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.close-modal {
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 24px;
}
/* Notification */
.notification {
position: fixed;
top: 20px;
right: 20px;
background: var(--accent);
color: #000;
padding: 16px 24px;
border-radius: 12px;
font-weight: 600;
z-index: 2000;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@media (max-width: 768px) {
.nav { display: none; }
.hero { padding: 60px 0; }
.features { padding: 60px 0; }
.set-row { grid-template-columns: 30px 1fr 1fr 50px; }
}
</style>
</head>
<body>
<!-- Landing Page -->
<div class="landing-container" id="landingPage">
<header>
<div class="container header-content">
<a href="#" class="logo">💪 MFP</a>
<nav class="nav">
<a href="#features">Возможности</a>
<a href="#how">Как работает</a>
<a href="#demo">Демо</a>
<a href="#reviews">Отзывы</a>
</nav>
<a href="#" class="cta-btn" onclick="openApp()">Открыть приложение</a>
</div>
</header>
<section class="hero">
<div class="container">
<h1>Твой персональный<br>AI-трекер тренировок</h1>
<p>Загружай программу, отслеживай прогресс, получай умные рекомендации по весам. Всё в одном месте.</p>
<div class="hero-buttons">
<a href="#" class="cta-btn btn-large" onclick="openApp()">Начать бесплатно</a>
<a href="#how" class="cta-btn btn-secondary btn-large">Узнать больше</a>
</div>
</div>
</section>
<section class="features" id="features">
<div class="container">
<h2 class="section-title">Всё для прогресса</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">📁</div>
<h3>Загрузка программы</h3>
<p>Импортируй свою программу тренировок из текста, Excel или создай вручную. Приложение распознает упражнения и подходы.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📈</div>
<h3>Умная прогрессия</h3>
<p>Система анализирует выполнение и автоматически предлагает увеличить вес на 2.5–5 кг когда ты готов.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤖</div>
<h3>AI Coach</h3>
<p>Персональный тренер отвечает на вопросы: почему нет прогресса, какой вес поставить, где плато.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📊</div>
<h3>Графики прогресса</h3>
<p>Визуализируй рост весов, количества повторений и общего объёма тренировок. Смотри динамику по месяцам.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🏆</div>
<h3>Личные рекорды</h3>
<p>Приложение автоматически отслеживает PR и поздравляет с новыми достижениями.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📱</div>
<h3>Telegram-бот</h3>
<p>Получай напоминания о тренировках и быстро записывай результаты прямо в мессенджере.</p>
</div>
</div>
</div>
</section>
<section class="how-it-works" id="how">
<div class="container">
<h2 class="section-title">Как это работает</h2>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h3>Регистрация</h3>
<p>Создай аккаунт за 30 секунд. Все данные хранятся локально в браузере.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h3>Настрой профиль</h3>
<p>Укажи рост, вес, цель тренировок и уровень подготовки для персонализации рекомендаций.</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h3>Загрузи программу</h3>
<p>Вставь текст программы или создай вручную. Приложение распознает упражнения.</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h3>Тренируйся</h3>
<p>Отмечай выполненные подходы, записывай фактический вес и повторения.</p>
</div>
</div>
<div class="step">
<div class="step-number">5</div>
<div class="step-content">
<h3>Получай рекомендации</h3>
<p>AI анализирует выполнение и предлагает оптимальную нагрузку на следующую тренировку.</p>
</div>
</div>
</div>
</div>
</section>
<section class="demo" id="demo">
<div class="container">
<h2 class="section-title">Интерфейс приложения</h2>
<div class="demo-content">
<div class="demo-header">
<div class="demo-dot red"></div>
<div class="demo-dot yellow"></div>
<div class="demo-dot green"></div>
</div>
<div class="demo-body">
<div class="demo-placeholder">
<div class="icon">💻</div>
<p>Нажми «Открыть приложение» чтобы попробовать</p>
</div>
</div>
</div>
</div>
</section>
<section class="testimonials" id="reviews">
<div class="container">
<h2 class="section-title">Отзывы</h2>
<div class="testimonials-grid">
<div class="testimonial-card">
<p class="testimonial-text">«Наконец-то перестал хаотично тренироваться! Приложение само говорит какой вес ставить. Жим вырос с 60 до 80 кг за 2 месяца.»</p>
<div class="testimonial-author">
<div class="author-avatar">А</div>
<div>
<div class="author-name">Алексей К.</div>
<div class="author-role">Новичок, 6 месяцев</div>
</div>
</div>
</div>
<div class="testimonial-card">
<p class="testimonial-text">«Удобно что можно загрузить свою программу. Не нужно переучиваться под готовые шаблоны. AI Coach реально помогает разобраться с плато.»</p>
<div class="testimonial-author">
<div class="author-avatar">Д</div>
<div>
<div class="author-name">Дмитрий В.</div>
<div class="author-role">Средний уровень, 1 год</div>
</div>
</div>
</div>
<div class="testimonial-card">
<p class="testimonial-text">«Графики мотивируют! Видишь как растёшь и не хочешь пропускать тренировки. Telegram-бот напоминает когда забывают.»</p>
<div class="testimonial-author">
<div class="author-avatar">М</div>
<div>
<div class="author-name">Мария С.</div>
<div class="author-role">Похудение, 4 месяца</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="cta">
<div class="container">
<h2>Готов начать прогрессировать?</h2>
<p>Бесплатно. Без скрытых платежей. Работает прямо в браузере.</p>
<a href="#" class="cta-btn btn-large" onclick="openApp()">Открыть приложение</a>
</div>
</section>
<footer>
<div class="container">
<div class="footer-links">
<a href="#features">Возможности</a>
<a href="#how">Как работает</a>
<a href="#">Политика конфиденциальности</a>
<a href="#">Контакты</a>
</div>
<p>&copy; 2025 My Fitness Progress. Все права защищены.</p>
</div>
</footer>
</div>
<!-- App Container -->
<div class="app-container" id="appContainer">
<header class="container app-header">
<a href="#" class="logo" onclick="closeApp()">💪 MFP</a>
<nav class="app-nav" id="appNav">
<button class="active" onclick="showTab('dashboard')">Dashboard</button>
<button onclick="showTab('workout')">Тренировка</button>
<button onclick="showTab('progress')">Прогресс</button>
<button onclick="showTab('program')">Программа</button>
<button onclick="showTab('profile')">Профиль</button>
</nav>
<button class="btn btn-small btn-secondary" onclick="logout()">Выход</button>
</header>
<div class="container">
<!-- Auth -->
<div id="authView" class="auth-box">
<h2>💪 Вход</h2>
<form id="loginForm">
<div class="form-group">
<label>Имя пользователя</label>
<input type="text" id="loginUsername" required>
</div>
<div class="form-group">
<label>Пароль</label>
<input type="password" id="loginPassword" required>
</div>
<button type="submit" class="btn">Войти</button>
</form>
<div class="auth-switch">
Нет аккаунта? <a onclick="showRegister()">Зарегистрироваться</a>
</div>
</div>
<div id="registerView" class="auth-box" style="display: none;">
<h2>📝 Регистрация</h2>
<form id="registerForm">
<div class="form-group">
<label>Имя пользователя</label>
<input type="text" id="regUsername" required>
</div>
<div class="form-group">
<label>Пароль</label>
<input type="password" id="regPassword" required>
</div>
<button type="submit" class="btn">Создать аккаунт</button>
</form>
<div class="auth-switch">
Есть аккаунт? <a onclick="showLogin()">Войти</a>
</div>
</div>
<!-- Dashboard -->
<div id="dashboardTab" class="dashboard">
<div class="stats-grid">
<div class="stat-card">
<div class="label">Тренировок</div>
<div class="value" id="statWorkouts">0</div>
</div>
<div class="stat-card">
<div class="label">Текущий вес</div>
<div class="value" id="statWeight">—</div>
</div>
<div class="stat-card">
<div class="label">Цель</div>
<div class="value" id="statGoal">—</div>
</div>
<div class="stat-card">
<div class="label">Прогресс</div>
<div class="value" id="statProgress">0%</div>
</div>
</div>
<div class="section">
<div class="section-title-small">
<h2>🔥 Последние тренировки</h2>
</div>
<div id="recentWorkouts"></div>
</div>
</div>
<!-- Workout -->
<div id="workoutTab" class="dashboard" style="display: none;">
<div class="section">
<div class="section-title-small">
<h2>📅 Сегодняшняя тренировка</h2>
<button class="btn btn-small" onclick="startWorkout()">Начать</button>
</div>
<div id="todayWorkout"></div>
</div>
</div>
<!-- Progress -->
<div id="progressTab" class="dashboard" style="display: none;">
<div class="section">
<div class="section-title-small"><h2>🏆 Личные рекорды</h2></div>
<div id="prList"></div>
</div>
</div>
<!-- Program -->
<div id="programTab" class="dashboard" style="display: none;">
<div class="section">
<div class="section-title-small">
<h2>📋 Моя программа</h2>
<button class="btn btn-small" onclick="showImportModal()">Загрузить</button>
</div>
<div id="programInfo"></div>
</div>
</div>
<!-- Profile -->
<div id="profileTab" class="dashboard" style="display: none;">
<div class="section">
<div class="section-title-small"><h2>👤 Профиль</h2></div>
<form id="profileForm" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px;">
<div class="form-group">
<label>Имя</label>
<input type="text" id="profileName">
</div>
<div class="form-group">
<label>Возраст</label>
<input type="number" id="profileAge">
</div>
<div class="form-group">
<label>Рост (см)</label>
<input type="number" id="profileHeight">
</div>
<div class="form-group">
<label>Начальный вес (кг)</label>
<input type="number" id="profileStartWeight" step="0.1">
</div>
<div class="form-group">
<label>Текущий вес (кг)</label>
<input type="number" id="profileCurrentWeight" step="0.1">
</div>
<div class="form-group">
<label>Целевой вес (кг)</label>
<input type="number" id="profileTargetWeight" step="0.1">
</div>
<div class="form-group" style="grid-column: 1 / -1;">
<button type="submit" class="btn">Сохранить</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Import Modal -->
<div class="modal" id="importModal">
<div class="modal-content">
<div class="modal-header">
<h3>📁 Загрузка программы</h3>
<button class="close-modal" onclick="closeImportModal()">×</button>
</div>
<div class="form-group">
<label>Название программы</label>
<input type="text" id="programName" placeholder="Например: 5x5 StrongLifts">
</div>
<div class="form-group">
<label>Программа (формат: Упражнение: подходыxповторения @ вес)</label>
<textarea id="programText" rows="12" style="width: 100%; padding: 12px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-family: monospace;" placeholder="День 1 - Грудь + Трицепс
Жим лёжа: 4x8 @ 60кг
Жим гантелей: 3x10 @ 20кг
Разгибание рук: 3x12 @ 15кг
День 2 - Спина + Бицепс
Тяга: 4x8 @ 70кг
Подтягивания: 3x8
Сгибание рук: 3x12 @ 12кг"></textarea>
</div>
<button class="btn" onclick="parseProgram()">Распознать и сохранить</button>
</div>
</div>
<!-- Workout Modal -->
<div class="modal" id="workoutModal">
<div class="modal-content">
<div class="modal-header">
<h3 id="workoutTitle">Тренировка</h3>
<button class="close-modal" onclick="closeWorkoutModal()">×</button>
</div>
<div id="workoutContent"></div>
<button class="btn" onclick="completeWorkout()" style="margin-top: 20px;">Завершить тренировку</button>
</div>
</div>
<script>
// Data storage in localStorage
function getAppData() {
try {
return JSON.parse(localStorage.getItem('mfp_data')) || { users: [], currentUser: null, programs: [], workouts: [] };
} catch {
return { users: [], currentUser: null, programs: [], workouts: [] };
}
}
function saveAppData(data) {
localStorage.setItem('mfp_data', JSON.stringify(data));
}
function hash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(36);
}
function genId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2);
}
let currentUser = null;
let currentProgram = null;
let activeWorkout = null;
// Landing <-> App
function openApp() {
document.getElementById('landingPage').classList.add('hidden');
document.getElementById('appContainer').classList.add('active');
checkAuth();
}
function closeApp() {
document.getElementById('landingPage').classList.remove('hidden');
document.getElementById('appContainer').classList.remove('active');
}
// Auth
function showLogin() {
document.getElementById('authView').style.display = 'block';
document.getElementById('registerView').style.display = 'none';
}
function showRegister() {
document.getElementById('authView').style.display = 'none';
document.getElementById('registerView').style.display = 'block';
}
function login(username, password) {
const data = getAppData();
const user = data.users.find(u => u.username === username && u.password === hash(password));
if (user) {
data.currentUser = user.id;
saveAppData(data);
currentUser = user;
showMain();
} else {
alert('Неверное имя или пароль');
}
}
function register(username, password) {
const data = getAppData();
if (data.users.find(u => u.username === username)) {
alert('Пользователь уже существует');
return;
}
const user = {
id: genId(),
username,
password: hash(password),
profile: {},
weightHistory: [],
achievedPRs: [],
createdAt: new Date().toISOString()
};
data.users.push(user);
data.currentUser = user.id;
saveAppData(data);
currentUser = user;
showMain();
}
function logout() {
const data = getAppData();
data.currentUser = null;
saveAppData(data);
currentUser = null;
document.getElementById('authView').style.display = 'block';
document.getElementById('registerView').style.display = 'none';
document.querySelectorAll('.dashboard').forEach(el => el.style.display = 'none');
}
function checkAuth() {
const data = getAppData();
if (data.currentUser) {
currentUser = data.users.find(u => u.id === data.currentUser);
if (currentUser) {
showMain();
}
}
}
function showMain() {
document.getElementById('authView').style.display = 'none';
document.getElementById('registerView').style.display = 'none';
document.querySelectorAll('.dashboard').forEach(el => el.style.display = 'none');
document.getElementById('dashboardTab').style.display = 'block';
document.querySelectorAll('#appNav button').forEach(el => el.classList.remove('active'));
document.querySelector('#appNav button:first-child').classList.add('active');
loadProfile();
loadProgram();
loadStats();
loadWorkouts();
}
function showTab(tab) {
document.querySelectorAll('.dashboard').forEach(el => el.style.display = 'none');
document.querySelectorAll('#appNav button').forEach(el => el.classList.remove('active'));
document.getElementById(tab + 'Tab').style.display = 'block';
event.target.classList.add('active');
if (tab === 'workout') loadTodayWorkout();
if (tab === 'progress') loadProgress();
if (tab === 'program') loadProgram();
}
// Profile
function loadProfile() {
if (!currentUser) return;
const profile = currentUser.profile || {};
document.getElementById('profileName').value = profile.name || '';
document.getElementById('profileAge').value = profile.age || '';
document.getElementById('profileHeight').value = profile.height || '';
document.getElementById('profileStartWeight').value = profile.startWeight || '';
document.getElementById('profileCurrentWeight').value = profile.currentWeight || '';
document.getElementById('profileTargetWeight').value = profile.targetWeight || '';
}
document.getElementById('profileForm').addEventListener('submit', (e) => {
e.preventDefault();
const data = getAppData();
const user = data.users.find(u => u.id === currentUser.id);
user.profile = {
name: document.getElementById('profileName').value,
age: parseInt(document.getElementById('profileAge').value),
height: parseInt(document.getElementById('profileHeight').value),
startWeight: parseFloat(document.getElementById('profileStartWeight').value),
currentWeight: parseFloat(document.getElementById('profileCurrentWeight').value),
targetWeight: parseFloat(document.getElementById('profileTargetWeight').value)
};
currentUser = user;
saveAppData(data);
showNotification('Профиль сохранён!');
loadStats();
});
// Program
function loadProgram() {
if (!currentUser) return;
const data = getAppData();
currentProgram = data.programs.find(p => p.userId === currentUser.id);
if (currentProgram) {
document.getElementById('programInfo').innerHTML = `
<div class="workout-card">
<h3>${currentProgram.name}</h3>
<p style="color: var(--text-muted);">${currentProgram.days.length} дней</p>
<div style="margin-top: 16px;">
${currentProgram.days.map((day, i) => `
<div style="margin-bottom: 16px;">
<strong>День ${i + 1}: ${day.name}</strong>
<ul style="color: var(--text-muted); margin-top: 8px; padding-left: 20px;">
${day.exercises.map(ex => `<li>${ex.name}: ${ex.sets}x${ex.targetReps} @ ${ex.currentWeight}кг</li>`).join('')}
</ul>
</div>
`).join('')}
</div>
</div>
`;
} else {
document.getElementById('programInfo').innerHTML = '<p style="color: var(--text-muted);">Программа не загружена. Нажми "Загрузить" чтобы добавить.</p>';
}
}
function showImportModal() {
document.getElementById('importModal').classList.add('on');
}
function closeImportModal() {
document.getElementById('importModal').classList.remove('on');
}
function parseProgram() {
const name = document.getElementById('programName').value || 'Моя программа';
const text = document.getElementById('programText').value;
const days = [];
const dayBlocks = text.split(/День\s*\d*[:\-\n]/i).filter(b => b.trim());
dayBlocks.forEach(block => {
const lines = block.trim().split('\n');
const dayName = lines[0].trim();
const exercises = [];
lines.slice(1).forEach(line => {
const match = line.match(/(.+?):\s*(\d+)x(\d+)\s*(?:@\s*)?(\d+(?:\.\d+)?)?/i);
if (match) {
exercises.push({
name: match[1].trim(),
sets: parseInt(match[2]),
targetReps: parseInt(match[3]),
startWeight: parseFloat(match[4]) || 0,
currentWeight: parseFloat(match[4]) || 0
});
}
});
if (exercises.length > 0) {
days.push({ name: dayName, exercises });
}
});
if (days.length === 0) {
alert('Не удалось распознать программу. Проверь формат.');
return;
}
const data = getAppData();
data.programs.push({
id: genId(),
userId: currentUser.id,
name,
days,
createdAt: new Date().toISOString()
});
saveAppData(data);
showNotification('Программа загружена!');
closeImportModal();
loadProgram();
}
// Workout
function loadTodayWorkout() {
if (!currentProgram) {
document.getElementById('todayWorkout').innerHTML = '<p style="color: var(--text-muted);">Сначала загрузи программу</p>';
return;
}
const dayIndex = new Date().getDay() % currentProgram.days.length;
const day = currentProgram.days[dayIndex];
document.getElementById('todayWorkout').innerHTML = `
<div class="workout-card">
<h3>${day.name}</h3>
<div style="color: var(--text-muted); margin-bottom: 16px;">
${day.exercises.length} упражнений
</div>
${day.exercises.map(ex => `
<div class="exercise-item">
<div class="exercise-header">
<span class="exercise-name">${ex.name}</span>
<span class="exercise-target">${ex.sets} × ${ex.targetReps} @ ${ex.currentWeight}кг</span>
</div>
</div>
`).join('')}
</div>
`;
}
function startWorkout() {
if (!currentProgram) {
alert('Сначала загрузи программу');
return;
}
const dayIndex = new Date().getDay() % currentProgram.days.length;
const day = currentProgram.days[dayIndex];
activeWorkout = {
id: genId(),
dayName: day.name,
exercises: day.exercises.map(ex => ({ ...ex, sets: [] })),
startedAt: new Date().toISOString()
};
showWorkoutModal();
}
function showWorkoutModal() {
document.getElementById('workoutTitle').textContent = activeWorkout.dayName;
const content = document.getElementById('workoutContent');
content.innerHTML = activeWorkout.exercises.map((ex, exIndex) => `
<div class="exercise-item" style="margin-bottom: 20px;">
<div class="exercise-header">
<span class="exercise-name">${ex.name}</span>
<span class="exercise-target">${ex.sets} × ${ex.targetReps} @ ${ex.currentWeight}кг</span>
</div>
<div class="sets-container">
${Array.from({ length: ex.sets }).map((_, i) => `
<div class="set-row">
<span style="color: var(--text-muted);">${i + 1}</span>
<input type="number" placeholder="кг" value="${ex.currentWeight}" id="set-${exIndex}-${i}-weight">
<input type="number" placeholder="повт" id="set-${exIndex}-${i}-reps">
<input type="number" placeholder="RPE" min="1" max="10" id="set-${exIndex}-${i}-rpe">
<input type="checkbox" class="set-check" id="set-${exIndex}-${i}-done">
</div>
`).join('')}
</div>
</div>
`).join('');
document.getElementById('workoutModal').classList.add('on');
}
function closeWorkoutModal() {
document.getElementById('workoutModal').classList.remove('on');
}
function completeWorkout() {
const setsData = [];
activeWorkout.exercises.forEach((ex, exIndex) => {
for (let i = 0; i < ex.sets; i++) {
const done = document.getElementById(`set-${exIndex}-${i}-done`).checked;
if (done) {
setsData.push({
exerciseIndex: exIndex,
set: {
weight: parseFloat(document.getElementById(`set-${exIndex}-${i}-weight`).value) || ex.currentWeight,
reps: parseInt(document.getElementById(`set-${exIndex}-${i}-reps`).value) || ex.targetReps,
rpe: parseInt(document.getElementById(`set-${exIndex}-${i}-rpe`).value) || 7,
completed: true
}
});
}
}
});
// Update weights for next time
setsData.forEach(({ exerciseIndex, set }) => {
if (set.completed && set.rpe <= 8) {
activeWorkout.exercises[exerciseIndex].currentWeight = parseFloat((parseFloat(activeWorkout.exercises[exerciseIndex].currentWeight) + 2.5).toFixed(1));
}
});
activeWorkout.completedAt = new Date().toISOString();
activeWorkout.status = 'completed';
const data = getAppData();
data.workouts.push(activeWorkout);
// Update program weights
if (currentProgram) {
const dayIndex = new Date().getDay() % currentProgram.days.length;
currentProgram.days[dayIndex].exercises = activeWorkout.exercises.map(ex => ({
name: ex.name,
sets: ex.sets,
targetReps: ex.targetReps,
startWeight: ex.startWeight,
currentWeight: ex.currentWeight
}));
}
// Check for PRs
const newPRs = [];
activeWorkout.exercises.forEach(ex => {
ex.sets.forEach(set => {
if (set.completed) {
const key = `${ex.name}_${set.weight}_${set.reps}`;
if (!currentUser.achievedPRs || !currentUser.achievedPRs.includes(key)) {
newPRs.push({ exercise: ex.name, weight: set.weight, reps: set.reps });
}
}
});
});
if (newPRs.length > 0) {
if (!currentUser.achievedPRs) currentUser.achievedPRs = [];
currentUser.achievedPRs.push(...newPRs.map(pr => `${pr.exercise}_${pr.weight}_${pr.reps}`));
const user = data.users.find(u => u.id === currentUser.id);
user.achievedPRs = currentUser.achievedPRs;
}
saveAppData(data);
if (newPRs.length > 0) {
newPRs.forEach(pr => {
showNotification(`🔥 Новый рекорд: ${pr.exercise} ${pr.weight}кг × ${pr.reps}!`);
});
} else {
showNotification('Тренировка завершена! Отличная работа! 💪');
}
closeWorkoutModal();
activeWorkout = null;
loadStats();
loadProgress();
}
function loadWorkouts() {
if (!currentUser) return;
const data = getAppData();
const workouts = data.workouts.filter(w => w.userId === currentUser.id && w.status === 'completed').slice(-5).reverse();
const container = document.getElementById('recentWorkouts');
if (workouts.length === 0) {
container.innerHTML = '<p style="color: var(--text-muted);">Пока нет тренировок</p>';
return;
}
container.innerHTML = workouts.map(w => `
<div class="workout-card" style="padding: 16px;">
<div style="display: flex; justify-content: space-between;">
<strong>${w.dayName}</strong>
<span style="color: var(--text-muted);">${new Date(w.completedAt).toLocaleDateString('ru-RU')}</span>
</div>
<div style="color: var(--text-muted); font-size: 14px; margin-top: 8px;">
${w.exercises.filter(e => e.sets.length > 0).length} упражнений выполнено
</div>
</div>
`).join('');
}
// Stats
function loadStats() {
if (!currentUser) return;
const data = getAppData();
const workouts = data.workouts.filter(w => w.userId === currentUser.id && w.status === 'completed');
const profile = currentUser.profile || {};
document.getElementById('statWorkouts').textContent = workouts.length;
document.getElementById('statWeight').textContent = profile.currentWeight ? profile.currentWeight + ' кг' : '—';
document.getElementById('statGoal').textContent = profile.targetWeight ? profile.targetWeight + ' кг' : '—';
if (profile.startWeight && profile.targetWeight && profile.currentWeight) {
const totalChange = profile.targetWeight - profile.startWeight;
const currentChange = profile.currentWeight - profile.startWeight;
const progress = totalChange !== 0 ? Math.min(100, Math.abs(currentChange / totalChange) * 100) : 0;
document.getElementById('statProgress').textContent = Math.round(progress) + '%';
}
}
function loadProgress() {
if (!currentUser) return;
const prList = document.getElementById('prList');
if (currentUser.achievedPRs && currentUser.achievedPRs.length > 0) {
prList.innerHTML = currentUser.achievedPRs.slice(-10).reverse().map(pr => {
const [ex, weight, reps] = pr.split('_');
return `<div class="workout-card" style="padding: 16px;">
<strong>${ex}</strong>: ${weight}кг × ${reps}
</div>`;
}).join('');
} else {
prList.innerHTML = '<p style="color: var(--text-muted);">Пока нет рекордов. Начни тренироваться!</p>';
}
}
// Utils
function showNotification(text) {
const el = document.createElement('div');
el.className = 'notification';
el.textContent = text;
document.body.appendChild(el);
setTimeout(() => el.remove(), 3000);
}
// Init
document.getElementById('loginForm').addEventListener('submit', (e) => {
e.preventDefault();
login(document.getElementById('loginUsername').value, document.getElementById('loginPassword').value);
});
document.getElementById('registerForm').addEventListener('submit', (e) => {
e.preventDefault();
register(document.getElementById('regUsername').value, document.getElementById('regPassword').value);
});
// Smooth scroll for landing
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const href = this.getAttribute('href');
if (href !== '#' && !this.onclick) {
e.preventDefault();
const target = document.querySelector(href);
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
}
}
});
});
</script>
</body>
</html>