/
ft_otaku
/
web
Обзор
Документация
Войти
/
ft_otaku
/
web
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.php
1 084 строки
39 KB
ft_otaku
Update: index.php
28 июл 2026, 17:20
Верифицирован
28 июл 2026, 17:20
a5c32a0
Код
Авторство
О чём код?
<?php // ====== КОНФИГУРАЦИЯ БАЗЫ ДАННЫХ ====== $host = 'localhost'; $user = 'root'; $pass = ''; $dbname = 'habit_tracker'; $conn = new mysqli($host, $user, $pass, $dbname); if ($conn->connect_error) { $conn = new mysqli($host, $user, $pass); $conn->query("CREATE DATABASE IF NOT EXISTS $dbname"); $conn->select_db($dbname); $conn->query("CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, habit VARCHAR(100) DEFAULT NULL, streak INT DEFAULT 0, last_date DATE DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )"); $conn->query("CREATE TABLE IF NOT EXISTS habit_marks ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, mark_date DATE NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_user_date (user_id, mark_date) )"); } session_start(); if (isset($_GET['action'])) { header('Content-Type: application/json'); $action = $_GET['action']; $data = json_decode(file_get_contents('php://input'), true); switch ($action) { case 'login': $username = $data['username'] ?? ''; $password = $data['password'] ?? ''; $result = $conn->query("SELECT * FROM users WHERE username = '$username'"); $user = $result->fetch_assoc(); if ($user && $password == $user['password']) { $_SESSION['user_id'] = $user['id']; unset($user['password']); echo json_encode(['success' => true, 'user' => $user]); } else { echo json_encode(['success' => false, 'error' => 'Неверный логин или пароль']); } exit; case 'register': $username = $data['username'] ?? ''; $password = $data['password'] ?? ''; $check = $conn->query("SELECT * FROM users WHERE username = '$username'"); if ($check->num_rows > 0) { echo json_encode(['success' => false, 'error' => 'Пользователь уже существует']); exit; } $conn->query("INSERT INTO users (username, password) VALUES ('$username', '$password')"); $id = $conn->insert_id; $_SESSION['user_id'] = $id; $user = $conn->query("SELECT * FROM users WHERE id = $id")->fetch_assoc(); unset($user['password']); echo json_encode(['success' => true, 'user' => $user]); exit; case 'getUser': if (!isset($_SESSION['user_id'])) { echo json_encode(['success' => false]); exit; } $user = $conn->query("SELECT * FROM users WHERE id = " . $_SESSION['user_id'])->fetch_assoc(); unset($user['password']); echo json_encode(['success' => true, 'user' => $user]); exit; case 'updateUser': $habit = $data['habit'] ?? ''; $id = $_SESSION['user_id']; $conn->query("UPDATE users SET habit = '$habit', streak = 0, last_date = NULL WHERE id = $id"); $conn->query("DELETE FROM habit_marks WHERE user_id = $id"); $user = $conn->query("SELECT * FROM users WHERE id = $id")->fetch_assoc(); unset($user['password']); echo json_encode(['success' => true, 'user' => $user]); exit; case 'markDay': $id = $_SESSION['user_id']; $today = date('Y-m-d'); $user = $conn->query("SELECT * FROM users WHERE id = $id")->fetch_assoc(); if ($user['streak'] >= 14) { echo json_encode(['success' => false, 'error' => 'Привычка уже завершена!']); exit; } $check = $conn->query("SELECT * FROM habit_marks WHERE user_id = $id AND mark_date = '$today'"); if ($check->num_rows > 0) { echo json_encode(['success' => false, 'error' => 'Уже отмечено сегодня']); exit; } $conn->query("INSERT INTO habit_marks (user_id, mark_date) VALUES ($id, '$today')"); $lastDate = $user['last_date']; $newStreak = 1; if ($lastDate) { $diff = (strtotime($today) - strtotime($lastDate)) / (60 * 60 * 24); if ($diff == 1) $newStreak = $user['streak'] + 1; elseif ($diff > 1) $newStreak = 1; } $conn->query("UPDATE users SET streak = $newStreak, last_date = '$today' WHERE id = $id"); $user = $conn->query("SELECT * FROM users WHERE id = $id")->fetch_assoc(); unset($user['password']); echo json_encode(['success' => true, 'user' => $user]); exit; case 'checkToday': $id = $_SESSION['user_id']; $today = date('Y-m-d'); $check = $conn->query("SELECT * FROM habit_marks WHERE user_id = $id AND mark_date = '$today'"); echo json_encode(['success' => true, 'marked' => $check->num_rows > 0]); exit; case 'logout': session_destroy(); echo json_encode(['success' => true]); exit; } } ?> <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Habit Killer — избавься от привычки за 14 дней</title> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', -apple-system, sans-serif; min-height: 100vh; background: #0a0a0f; background-image: radial-gradient(ellipse at 20% 50%, rgba(99, 102, 241, 0.08) 0%, transparent 60%), radial-gradient(ellipse at 80% 50%, rgba(168, 85, 247, 0.08) 0%, transparent 60%); display: flex; justify-content: center; align-items: center; padding: 20px; } .page { display: none; width: 100%; max-width: 440px; animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1); } .page.active { display: block; } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } } @keyframes glow { 0%, 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.2); } 50% { box-shadow: 0 0 40px rgba(99, 102, 241, 0.4); } } .glass { background: rgba(255, 255, 255, 0.04); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 24px; padding: 2rem; box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5); } h2 { font-size: 1.75rem; font-weight: 800; text-align: center; background: linear-gradient(135deg, #818cf8 0%, #a78bfa 50%, #c084fc 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 1.5rem; letter-spacing: -0.5px; } .subtitle { text-align: center; color: rgba(255, 255, 255, 0.4); font-size: 0.85rem; margin-top: -0.5rem; margin-bottom: 1.5rem; font-weight: 500; letter-spacing: 0.3px; } input { width: 100%; padding: 14px 18px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 14px; color: #fff; font-size: 1rem; font-family: 'Inter', sans-serif; transition: all 0.3s ease; margin: 6px 0; } input::placeholder { color: rgba(255, 255, 255, 0.25); font-weight: 400; } input:focus { outline: none; border-color: rgba(99, 102, 241, 0.5); background: rgba(255, 255, 255, 0.08); box-shadow: 0 0 30px rgba(99, 102, 241, 0.1); } select { width: 100%; padding: 14px 18px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 14px; color: #fff; font-size: 1rem; font-family: 'Inter', sans-serif; transition: all 0.3s ease; margin: 6px 0; appearance: none; cursor: pointer; } select option { background: #1a1a2e; color: #fff; } select:focus { outline: none; border-color: rgba(99, 102, 241, 0.5); } .btn { width: 100%; padding: 14px; border: none; border-radius: 14px; font-size: 1rem; font-weight: 700; font-family: 'Inter', sans-serif; cursor: pointer; transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); letter-spacing: 0.3px; position: relative; overflow: hidden; } .btn::after { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(255,255,255,0.1), transparent); opacity: 0; transition: opacity 0.3s ease; } .btn:hover::after { opacity: 1; } .btn:hover:not(:disabled) { transform: translateY(-2px); } .btn:active:not(:disabled) { transform: translateY(0) scale(0.98); } .btn-primary { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); color: #fff; box-shadow: 0 4px 20px rgba(99, 102, 241, 0.3); } .btn-primary:hover:not(:disabled) { box-shadow: 0 6px 30px rgba(99, 102, 241, 0.5); transform: translateY(-2px); } .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; } .btn-success { background: linear-gradient(135deg, #10b981 0%, #34d399 100%); color: #fff; box-shadow: 0 4px 20px rgba(16, 185, 129, 0.3); } .btn-success:hover:not(:disabled) { box-shadow: 0 6px 30px rgba(16, 185, 129, 0.5); transform: translateY(-2px); } .btn-warning { background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%); color: #1a1a2e; box-shadow: 0 4px 20px rgba(245, 158, 11, 0.2); } .btn-warning:hover:not(:disabled) { box-shadow: 0 6px 30px rgba(245, 158, 11, 0.4); transform: translateY(-2px); } .btn-outline { background: transparent; color: rgba(255, 255, 255, 0.6); border: 1px solid rgba(255, 255, 255, 0.1); } .btn-outline:hover { background: rgba(255, 255, 255, 0.05); border-color: rgba(255, 255, 255, 0.2); color: #fff; } .btn-danger { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.2); width: auto; padding: 8px 18px; font-size: 0.85rem; } .btn-danger:hover { background: rgba(239, 68, 68, 0.25); } .btn-sm { width: auto; padding: 10px 20px; font-size: 0.85rem; } .btn-profile { background: rgba(139, 92, 246, 0.15); color: #a78bfa; border: 1px solid rgba(139, 92, 246, 0.2); width: auto; padding: 8px 18px; font-size: 0.85rem; } .btn-profile:hover { background: rgba(139, 92, 246, 0.25); } .link { text-align: center; margin-top: 1.2rem; color: rgba(255, 255, 255, 0.35); cursor: pointer; font-weight: 500; font-size: 0.9rem; transition: color 0.3s ease; } .link:hover { color: rgba(167, 139, 250, 0.8); } .error { color: #f87171; font-size: 0.85rem; text-align: center; min-height: 1.4rem; margin-top: 4px; } .message { text-align: center; margin-top: 1rem; padding: 12px 16px; border-radius: 12px; font-weight: 600; font-size: 0.9rem; animation: slideUp 0.3s ease; } .message.success { background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.2); } .message.error { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.2); } .message.completed { background: rgba(245, 158, 11, 0.15); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.2); animation: pulse 2s ease-in-out infinite; } .header { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 1.5rem; flex-wrap: wrap; } .header h2 { margin-bottom: 0; flex: 1; font-size: 1.5rem; } .header .btn { width: auto; } .habit-info { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 16px; padding: 1.5rem; margin-bottom: 1.5rem; text-align: center; } .habit-info .label { color: rgba(255, 255, 255, 0.3); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; font-weight: 600; } .habit-info .habit-name { color: #fff; font-size: 1.2rem; font-weight: 700; margin: 6px 0 12px 0; } .habit-info .streak-number { font-size: 3.5rem; font-weight: 900; background: linear-gradient(135deg, #818cf8 0%, #c084fc 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; } .habit-info .streak-label { color: rgba(255, 255, 255, 0.3); font-size: 0.85rem; margin-top: 4px; } .habit-info .progress-bar { width: 100%; height: 4px; background: rgba(255, 255, 255, 0.06); border-radius: 2px; margin-top: 16px; overflow: hidden; } .habit-info .progress-fill { height: 100%; background: linear-gradient(90deg, #6366f1, #a78bfa); border-radius: 2px; transition: width 0.6s cubic-bezier(0.16, 1, 0.3, 1); } .profile-info { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 16px; padding: 1.5rem; margin-bottom: 1.5rem; } .profile-info p { color: rgba(255, 255, 255, 0.7); margin: 10px 0; font-size: 0.95rem; display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.03); } .profile-info p:last-child { border-bottom: none; } .profile-info p strong { color: rgba(255, 255, 255, 0.4); font-weight: 500; } .profile-info p span { color: #fff; font-weight: 600; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); display: flex; justify-content: center; align-items: center; z-index: 1000; padding: 20px; } .modal.hidden { display: none; } .modal-content { background: rgba(26, 26, 46, 0.95); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 24px; padding: 2rem; max-width: 480px; width: 100%; max-height: 90vh; overflow-y: auto; animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1); } .modal-content h3 { color: #fff; font-size: 1.4rem; font-weight: 700; text-align: center; margin-bottom: 4px; } .modal-content .modal-sub { color: rgba(255, 255, 255, 0.3); font-size: 0.85rem; text-align: center; margin-bottom: 1.2rem; } .category-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 12px 0; justify-content: center; } .category-tabs .cat-btn { padding: 6px 16px; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 20px; background: rgba(255, 255, 255, 0.03); color: rgba(255, 255, 255, 0.4); font-size: 0.8rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; font-family: 'Inter', sans-serif; } .category-tabs .cat-btn:hover { background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.7); } .category-tabs .cat-btn.active { background: rgba(99, 102, 241, 0.2); border-color: rgba(99, 102, 241, 0.3); color: #a78bfa; } .habit-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 12px 0; } .habit-item { padding: 10px 12px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 12px; color: rgba(255, 255, 255, 0.6); font-size: 0.85rem; cursor: pointer; transition: all 0.3s ease; text-align: center; font-weight: 500; } .habit-item:hover { background: rgba(255, 255, 255, 0.06); color: #fff; transform: scale(1.02); } .habit-item.selected { background: rgba(99, 102, 241, 0.15); border-color: rgba(99, 102, 241, 0.3); color: #a78bfa; box-shadow: 0 0 20px rgba(99, 102, 241, 0.05); } .modal-content .btn { margin-top: 8px; } .custom-input { margin-top: 10px; } .footer-text { text-align: center; margin-top: 1rem; color: rgba(255, 255, 255, 0.12); font-size: 0.7rem; letter-spacing: 0.5px; font-weight: 500; } /* Scrollbar */ ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(99, 102, 241, 0.3); border-radius: 2px; } @media (max-width: 480px) { .glass { padding: 1.5rem; } .habit-grid { grid-template-columns: 1fr 1fr; } .header { flex-direction: column; align-items: stretch; } .header .btn { width: 100%; } .habit-info .streak-number { font-size: 2.8rem; } } </style> </head> <body> <div id="app"> <!-- Вход --> <div id="loginPage" class="page active"> <div class="glass"> <h2>✦ Habit Killer</h2> <div class="subtitle">Избавься от привычки за 14 дней</div> <input type="text" id="loginUsername" placeholder="Имя пользователя"> <input type="password" id="loginPassword" placeholder="Пароль"> <div id="loginError" class="error"></div> <button class="btn btn-primary" onclick="login()">Войти</button> <div class="link" onclick="showPage('registerPage')">Нет аккаунта? Создать</div> </div> </div> <!-- Регистрация --> <div id="registerPage" class="page"> <div class="glass"> <h2>✦ Создать аккаунт</h2> <div class="subtitle">Начни свой путь к свободе</div> <input type="text" id="regUsername" placeholder="Имя пользователя"> <input type="password" id="regPassword" placeholder="Пароль (мин. 3 символа)"> <div id="regError" class="error"></div> <button class="btn btn-primary" onclick="register()">Зарегистрироваться</button> <div class="link" onclick="showPage('loginPage')">Уже есть аккаунт? Войти</div> </div> </div> <!-- Главная --> <div id="mainPage" class="page"> <div class="glass"> <div class="header"> <h2>✦</h2> <button class="btn btn-profile btn-sm" onclick="showProfile()">👤 Профиль</button> <button class="btn btn-danger btn-sm" onclick="logout()">Выйти</button> </div> <div class="habit-info"> <div class="label">Привычка</div> <div class="habit-name" id="habitDisplay">не выбрана</div> <div class="streak-number" id="streakDisplay">0</div> <div class="streak-label">дней подряд</div> <div class="progress-bar"> <div class="progress-fill" id="progressFill" style="width: 0%;"></div> </div> </div> <button class="btn btn-success" id="markDayBtn" onclick="markDay()">✅ Отметить сегодня</button> <button class="btn btn-warning" onclick="showHabitModal()" style="margin-top:8px;">🔄 Сменить привычку</button> <div id="mainMessage" class="message"></div> <div class="footer-text">14 дней — и привычка побеждена</div> </div> </div> <!-- Профиль --> <div id="profilePage" class="page"> <div class="glass"> <h2>✦ Профиль</h2> <div class="profile-info"> <p><strong>Имя</strong> <span id="profileUsername"></span></p> <p><strong>Привычка</strong> <span id="profileHabit"></span></p> <p><strong>Дней подряд</strong> <span id="profileStreak"></span></p> <p><strong>Регистрация</strong> <span id="profileCreated"></span></p> <p><strong>Статус</strong> <span id="profileStatus"></span></p> </div> <button class="btn btn-outline" onclick="showPage('mainPage')">← На главную</button> </div> </div> </div> <!-- Модалка --> <div id="habitModal" class="modal hidden"> <div class="modal-content"> <h3>🎯 Выбери привычку</h3> <div class="modal-sub">Или напиши свою</div> <div class="category-tabs" id="categoryTabs"></div> <div class="habit-grid" id="habitList"></div> <input type="text" id="customHabit" placeholder="✏️ Своя привычка..." class="custom-input"> <button class="btn btn-primary" onclick="saveHabit()">💾 Сохранить</button> <button class="btn btn-outline" onclick="hideModal()">Отмена</button> </div> </div> <script> const habitsByCategory = { '🚬 Вредные': [ 'Курение', 'Вейпинг', 'Алкоголь', 'Энергетики', 'Кофеин (более 3 чашек)', 'Сладкое', 'Фастфуд', 'Солёные снеки', 'Переедание', 'Еда ночью' ], '📱 Цифровые': [ 'Соцсети (зависание)', 'TikTok (более часа)', 'Instagram (более часа)', 'YouTube (более 2ч)', 'Телефон перед сном', 'Мобильные игры', 'Сериалы (запоем)', 'Интернет-шопинг', 'Бесполезный серфинг' ], '🧠 Психологические': [ 'Прокрастинация', 'Оправдания', 'Самокритика', 'Сравнение с другими', 'Сплетни', 'Грызть ногти', 'Ковырять кожу', 'Откладывание дел', 'Страх нового' ], '💤 Здоровье': [ 'Поздний отбой (после 1ч)', 'Недосып (менее 6ч)', 'Пропуск завтрака', 'Малоподвижность', 'Сутулость', 'Долгое сидение', 'Отказ от воды', 'Плохая осанка' ], '💰 Финансовые': [ 'Импульсивные покупки', 'Траты на ерунду', 'Азартные игры', 'Доставка еды', 'Спонтанные траты', 'Игнорирование бюджета' ] }; const categoryNames = Object.keys(habitsByCategory); let selectedHabit = ''; let currentCategory = categoryNames[0]; let currentUser = null; let isTodayMarked = false; function renderCategories() { const container = document.getElementById('categoryTabs'); container.innerHTML = categoryNames.map(cat => `<button class="cat-btn ${cat === currentCategory ? 'active' : ''}" onclick="selectCategory('${cat}')">${cat}</button>` ).join(''); } function selectCategory(cat) { currentCategory = cat; renderCategories(); renderHabits(); } function renderHabits() { const container = document.getElementById('habitList'); const habits = habitsByCategory[currentCategory] || []; container.innerHTML = habits.map(h => `<div class="habit-item ${selectedHabit === h ? 'selected' : ''}" onclick="selectHabit('${h}')">${h}</div>` ).join(''); } function selectHabit(habit) { selectedHabit = habit; document.getElementById('customHabit').value = ''; renderHabits(); } document.addEventListener('DOMContentLoaded', function() { const customInput = document.getElementById('customHabit'); if (customInput) { customInput.addEventListener('input', function() { if (this.value.trim()) { selectedHabit = this.value.trim(); renderHabits(); document.querySelectorAll('.habit-item').forEach(el => el.classList.remove('selected')); } }); } }); function showPage(pageId) { document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); document.getElementById(pageId).classList.add('active'); } function showError(id, msg) { document.getElementById(id).textContent = msg; } function showMessage(msg, type = 'success') { const el = document.getElementById('mainMessage'); el.textContent = msg; el.className = 'message ' + type; if (type === 'completed') { el.style.animation = 'pulse 2s ease-in-out infinite'; } } async function api(action, data = null) { const options = { method: 'POST', headers: { 'Content-Type': 'application/json' } }; if (data) options.body = JSON.stringify(data); const url = window.location.pathname + '?action=' + action; const response = await fetch(url, options); return await response.json(); } async function login() { const username = document.getElementById('loginUsername').value.trim(); const password = document.getElementById('loginPassword').value.trim(); if (!username || !password) { showError('loginError', 'Заполните все поля'); return; } const result = await api('login', { username, password }); if (result.success) { currentUser = result.user; await checkToday(); showPage('mainPage'); updateUI(); if (!currentUser.habit) showHabitModal(); } else { showError('loginError', result.error); } } async function register() { const username = document.getElementById('regUsername').value.trim(); const password = document.getElementById('regPassword').value.trim(); if (!username || !password) { showError('regError', 'Заполните все поля'); return; } if (password.length < 3) { showError('regError', 'Пароль минимум 3 символа'); return; } const result = await api('register', { username, password }); if (result.success) { currentUser = result.user; await checkToday(); showPage('mainPage'); updateUI(); if (!currentUser.habit) showHabitModal(); } else { showError('regError', result.error); } } async function logout() { await api('logout'); currentUser = null; showPage('loginPage'); } async function checkAuth() { const result = await api('getUser'); if (result.success) { currentUser = result.user; await checkToday(); showPage('mainPage'); updateUI(); if (!currentUser.habit) showHabitModal(); if (currentUser.streak >= 14) { showMessage('🏆 Поздравляем! Ты победил привычку! 🎉', 'completed'); } } } async function checkToday() { const result = await api('checkToday'); if (result.success) { isTodayMarked = result.marked; updateMarkButton(); } } async function markDay() { if (isTodayMarked) { showMessage('Сегодня уже отмечено!', 'error'); return; } if (currentUser && currentUser.streak >= 14) { showMessage('Привычка уже побеждена!', 'error'); return; } const result = await api('markDay'); if (result.success) { currentUser = result.user; isTodayMarked = true; updateUI(); updateMarkButton(); if (currentUser.streak >= 14) { showMessage('🎉🏆 Ты сделал это! Привычка побеждена!', 'completed'); } else { showMessage(`✅ Отлично! Осталось ${14 - currentUser.streak} дней до победы!`, 'success'); } } else { showMessage(result.error || 'Ошибка', 'error'); } } async function saveHabit() { const customInput = document.getElementById('customHabit'); let habit = selectedHabit; if (customInput && customInput.value.trim()) { habit = customInput.value.trim(); } if (!habit) { showMessage('Выбери или введи привычку!', 'error'); return; } const result = await api('updateUser', { habit }); if (result.success) { currentUser = result.user; isTodayMarked = false; updateUI(); hideModal(); showMessage(`✅ Привычка "${habit}" сохранена! Начинай заново.`, 'success'); } } function showHabitModal() { selectedHabit = currentUser?.habit || ''; currentCategory = categoryNames[0]; renderCategories(); renderHabits(); if (selectedHabit) { document.getElementById('customHabit').value = ''; } document.getElementById('habitModal').classList.remove('hidden'); } function hideModal() { document.getElementById('habitModal').classList.add('hidden'); } function showProfile() { if (currentUser) { document.getElementById('profileUsername').textContent = currentUser.username; document.getElementById('profileHabit').textContent = currentUser.habit || 'не выбрана'; document.getElementById('profileStreak').textContent = currentUser.streak || 0; document.getElementById('profileCreated').textContent = currentUser.created_at ? new Date(currentUser.created_at).toLocaleDateString('ru-RU') : 'неизвестно'; const status = currentUser.streak >= 14 ? '✅ Победа!' : `⏳ ${currentUser.streak || 0}/14`; document.getElementById('profileStatus').textContent = status; showPage('profilePage'); } } function updateUI() { if (!currentUser) return; document.getElementById('habitDisplay').textContent = currentUser.habit || 'не выбрана'; document.getElementById('streakDisplay').textContent = currentUser.streak || 0; const progress = Math.min((currentUser.streak / 14) * 100, 100); document.getElementById('progressFill').style.width = progress + '%'; const markBtn = document.getElementById('markDayBtn'); if (currentUser.streak >= 14) { markBtn.disabled = true; markBtn.textContent = '🏆 Победа!'; markBtn.style.opacity = '0.6'; } else { updateMarkButton(); } } function updateMarkButton() { const btn = document.getElementById('markDayBtn'); if (currentUser && currentUser.streak >= 14) { btn.disabled = true; btn.textContent = '🏆 Победа!'; btn.style.opacity = '0.6'; return; } if (isTodayMarked) { btn.disabled = true; btn.textContent = '✅ Отмечено сегодня'; btn.style.opacity = '0.6'; } else { btn.disabled = false; btn.textContent = '✅ Отметить сегодня'; btn.style.opacity = '1'; } } // Закрытие модалки по клику вне document.getElementById('habitModal').addEventListener('click', function(e) { if (e.target === this) { // Не закрываем } }); window.onload = function() { renderCategories(); renderHabits(); checkAuth(); }; </script> </body> </html>