845 lines
52 KiB
HTML
845 lines
52 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||
<title>Галикон — спорт будущего</title>
|
||
<link rel="manifest" href="manifest.json">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta name="apple-mobile-web-app-title" content="Галикон">
|
||
<meta name="theme-color" content="#0F1218">
|
||
<style>
|
||
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--red:#FF6B6B;--green:#4CAF50;--gold:#FFD700}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--ink);overflow-x:hidden}
|
||
input,select,textarea{width:100%;padding:14px 16px;border:2px solid #2a3342;border-radius:12px;font:inherit;font-size:16px;background:#1a2332;color:var(--white);transition:border .2s}
|
||
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--cyan)}
|
||
input::placeholder{color:#5b6573}
|
||
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:14px;font-weight:700;border:none;font-size:16px;cursor:pointer;transition:all .2s;text-align:center;width:100%}
|
||
.btn:active{transform:scale(.97)}
|
||
.btn.outline{background:transparent;border:2px solid var(--cyan);color:var(--cyan)}
|
||
.btn.small{padding:8px 16px;font-size:13px;width:auto}
|
||
.btn.danger{background:var(--red);color:#fff}
|
||
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--cyan);color:var(--ink);padding:12px 24px;border-radius:30px;font-weight:700;font-size:14px;z-index:999;opacity:0;transition:opacity .3s;pointer-events:none}
|
||
.toast.show{opacity:1}
|
||
|
||
/* SCREENS */
|
||
.screen{position:fixed;top:0;left:0;right:0;bottom:0;display:none;flex-direction:column;background:var(--ink);color:var(--white);overflow-y:auto;z-index:10}
|
||
.screen.active{display:flex}
|
||
|
||
/* LOGIN */
|
||
.login-box{max-width:400px;margin:auto;padding:24px;width:100%}
|
||
.login-box h1{text-align:center;font-size:32px;margin-bottom:8px}
|
||
.login-box h1 span{color:var(--cyan)}
|
||
.login-box .sub{text-align:center;color:var(--gray-500);margin-bottom:24px;font-size:15px}
|
||
.login-box .error{color:var(--red);font-size:13px;margin-bottom:8px;display:none}
|
||
|
||
/* REGISTRATION STEPS */
|
||
.reg-step{display:none;flex-direction:column;gap:8px;max-width:400px;margin:auto;padding:24px;width:100%}
|
||
.reg-step.active{display:flex}
|
||
.reg-step h2{text-align:center;font-size:22px;margin-bottom:4px}
|
||
.reg-step .hint{text-align:center;color:var(--gray-500);font-size:13px;margin-bottom:16px}
|
||
.step-indicator{display:flex;justify-content:center;gap:6px;margin-bottom:16px}
|
||
.step-dot{width:8px;height:8px;border-radius:50%;background:#2a3342;transition:all .3s}
|
||
.step-dot.done{background:var(--cyan)}
|
||
.step-dot.current{background:var(--cyan);box-shadow:0 0 8px var(--cyan)}
|
||
.reg-nav{display:flex;gap:8px;margin-top:8px}
|
||
.reg-nav button{flex:1}
|
||
|
||
/* AVATAR PICKER */
|
||
.avatar-picker{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:12px 0}
|
||
.avatar-opt{width:56px;height:56px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:28px;cursor:pointer;border:3px solid transparent;transition:all .2s;background:#1a2332}
|
||
.avatar-opt.selected{border-color:var(--cyan);transform:scale(1.1)}
|
||
.avatar-opt img{width:100%;height:100%;border-radius:50%;object-fit:cover}
|
||
|
||
/* BOTTOM NAV */
|
||
.bottom-nav{display:flex;justify-content:space-around;padding:8px;background:#151c28;border-top:1px solid #2a3342;position:sticky;bottom:0;z-index:50}
|
||
.nav-item{display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 12px;border-radius:12px;cursor:pointer;color:var(--gray-500);font-size:11px;transition:all .2s;border:none;background:none}
|
||
.nav-item.active{color:var(--cyan)}
|
||
.nav-item .icon{font-size:22px}
|
||
|
||
/* CONTENT */
|
||
.content{flex:1;overflow-y:auto;padding:16px;max-width:700px;margin:0 auto;width:100%}
|
||
.card{background:#151c28;border-radius:16px;padding:20px;margin-bottom:12px;border:1px solid #2a3342}
|
||
.card h3{font-size:17px;font-weight:700;margin-bottom:10px}
|
||
.card .muted{color:var(--gray-500);font-size:13px}
|
||
|
||
.profile-header{text-align:center;padding:24px 16px}
|
||
.profile-header .avatar{width:80px;height:80px;border-radius:50%;background:var(--cyan);color:var(--ink);display:flex;align-items:center;justify-content:center;font-size:36px;font-weight:800;margin:0 auto 12px;overflow:hidden}
|
||
.profile-header .avatar img{width:100%;height:100%;object-fit:cover}
|
||
.profile-header h2{font-size:22px}
|
||
.profile-header .tag{display:inline-block;background:var(--cyan);color:var(--ink);padding:3px 12px;border-radius:12px;font-size:12px;font-weight:700;margin-top:4px}
|
||
|
||
.achievement-row{display:flex;gap:8px;flex-wrap:wrap}
|
||
.achievement-badge{background:#1a2332;border-radius:12px;padding:10px 14px;font-size:13px;text-align:center}
|
||
.achievement-badge .val{font-size:20px;font-weight:800;color:var(--cyan)}
|
||
|
||
table{width:100%;border-collapse:collapse;font-size:13px}
|
||
th,td{padding:8px;text-align:left;border-bottom:1px solid #2a3342}
|
||
th{color:var(--gray-500);font-size:11px;text-transform:uppercase}
|
||
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:600}
|
||
.badge.gold{background:#FFF3CD;color:#856404}
|
||
.badge.blue{background:var(--cyan-50);color:var(--ink)}
|
||
.badge.green{background:#D4EDDA;color:#155724}
|
||
|
||
/* CHAT */
|
||
.chat-list-item{display:flex;align-items:center;gap:12px;padding:12px;background:#1a2332;border-radius:12px;margin-bottom:6px;cursor:pointer;transition:background .2s}
|
||
.chat-list-item:active{background:#2a3342}
|
||
.chat-list-item .av{width:40px;height:40px;border-radius:50%;background:var(--cyan);color:var(--ink);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:18px;flex-shrink:0}
|
||
.chat-list-item .info{flex:1;min-width:0}
|
||
.chat-list-item .name{font-weight:600;font-size:14px}
|
||
.chat-list-item .last{font-size:12px;color:var(--gray-500);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.chat-list-item .time{font-size:11px;color:var(--gray-500)}
|
||
.chat-list-item .unread{background:var(--cyan);color:var(--ink);border-radius:10px;padding:1px 7px;font-size:11px;font-weight:700}
|
||
|
||
.chat-messages{display:flex;flex-direction:column;gap:8px;padding:8px 0;flex:1;overflow-y:auto}
|
||
.chat-msg{max-width:80%;padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.4;word-break:break-word}
|
||
.chat-msg.mine{background:var(--cyan);color:var(--ink);align-self:flex-end;border-bottom-right-radius:4px}
|
||
.chat-msg.theirs{background:#1a2332;color:var(--white);align-self:flex-start;border-bottom-left-radius:4px}
|
||
.chat-msg .sender{font-size:11px;color:var(--gray-500);margin-bottom:2px}
|
||
.chat-input-row{display:flex;gap:8px;padding:8px 0}
|
||
.chat-input-row input{flex:1;margin-bottom:0}
|
||
|
||
.chat-tabs{display:flex;gap:4px;margin-bottom:12px}
|
||
.chat-tab{flex:1;padding:8px;border-radius:10px;border:none;font-size:13px;font-weight:600;cursor:pointer;background:#1a2332;color:var(--gray-500);transition:all .2s}
|
||
.chat-tab.active{background:var(--cyan);color:var(--ink)}
|
||
|
||
|
||
.game-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:180px;margin:12px auto}
|
||
.game-cell{aspect-ratio:1;background:#1a2332;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:800;cursor:pointer;transition:background .2s;border:2px solid #2a3342}
|
||
.game-cell:active{background:#2a3342}
|
||
.game-cell.x{color:var(--cyan)}
|
||
.game-cell.o{color:var(--red)}
|
||
.game-cell.disabled{pointer-events:none}
|
||
.game-status{text-align:center;font-size:14px;font-weight:600;margin:8px 0}
|
||
.quiz-option{display:block;width:100%;padding:12px;margin:6px 0;background:#1a2332;border:2px solid #2a3342;border-radius:10px;color:var(--white);font-size:14px;cursor:pointer;text-align:left;transition:all .2s}
|
||
.quiz-option:active{background:#2a3342}
|
||
.quiz-option.right{background:var(--green);border-color:var(--green);color:#fff}
|
||
.quiz-option.wrong{background:var(--red);border-color:var(--red);color:#fff}
|
||
|
||
.empty-state{text-align:center;padding:40px 20px;color:var(--gray-500)}
|
||
.empty-state .big{font-size:48px;margin-bottom:8px}
|
||
|
||
.progress-bar{height:6px;background:#1a2332;border-radius:3px;overflow:hidden;margin-top:6px}
|
||
.progress-bar .fill{height:100%;background:var(--cyan);border-radius:3px}
|
||
|
||
.lightbox{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.95);z-index:200;display:flex;align-items:center;justify-content:center;cursor:pointer}
|
||
.lightbox img,.lightbox video{max-width:95%;max-height:90vh;border-radius:8px}
|
||
|
||
label.file-btn{display:inline-block;background:#1a2332;padding:12px 20px;border-radius:12px;font-weight:600;cursor:pointer;font-size:14px;margin:8px 0}
|
||
label.file-btn:active{background:#2a3342}
|
||
input[type=file]{display:none}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- LOGIN SCREEN -->
|
||
<div class="screen active" id="loginScreen">
|
||
<div class="login-box">
|
||
<h1>🏊 <span>Галикон</span></h1>
|
||
<p class="sub">Спорт будущего</p>
|
||
<input type="text" id="lUser" placeholder="Логин">
|
||
<input type="password" id="lPass" placeholder="Пароль">
|
||
<div class="error" id="loginErr"></div>
|
||
<button class="btn" onclick="doLogin()">🔒 Войти</button>
|
||
<div style="text-align:center;margin-top:12px">
|
||
<button class="btn outline" onclick="startReg()" style="width:auto;padding:12px 40px">✏ Регистрация</button>
|
||
</div>
|
||
<p style="text-align:center;color:var(--gray-500);font-size:12px;margin-top:16px">Нет аккаунта? Создай за 1 минуту</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- REGISTRATION SCREEN -->
|
||
<div class="screen" id="regScreen">
|
||
<div style="padding:16px;display:flex;align-items:center;justify-content:space-between">
|
||
<button class="btn small outline" onclick="backToLogin()">← Назад</button>
|
||
<span style="font-weight:700;color:var(--cyan)">Шаг <span id="stepNum">1</span>/7</span>
|
||
</div>
|
||
<div class="step-indicator" id="stepDots"></div>
|
||
|
||
<!-- Step 1: Name -->
|
||
<div class="reg-step active" data-step="1">
|
||
<h2>✏ Как тебя зовут?</h2>
|
||
<p class="hint">Введи свои Фамилию, Имя и Отчество</p>
|
||
<input type="text" id="rName" placeholder="Например: Кайрат Гали Аскарович" autofocus>
|
||
<div class="reg-nav"><button class="btn" onclick="nextStep()">Дальше →</button></div>
|
||
</div>
|
||
<!-- Step 2: Login -->
|
||
<div class="reg-step" data-step="2">
|
||
<h2>🔑 Придумай логин и пароль</h2>
|
||
<p class="hint">Логин — твоё имя в приложении. Пароль — секрет.</p>
|
||
<input type="text" id="rLogin" placeholder="Логин (латиница, без пробелов)">
|
||
<input type="password" id="rPass" placeholder="Пароль (минимум 3 символа)">
|
||
<div class="reg-nav">
|
||
<button class="btn outline" onclick="prevStep()">← Назад</button>
|
||
<button class="btn" onclick="nextStep()">Дальше →</button>
|
||
</div>
|
||
</div>
|
||
<!-- Step 3: Sport -->
|
||
<div class="reg-step" data-step="3">
|
||
<h2>🏊 Твой вид спорта</h2>
|
||
<p class="hint">Выбери из списка олимпийских видов</p>
|
||
<select id="rSport"><option value="">Выбери вид спорта</option><option>Академическая гребля</option><option>Бадминтон</option><option>Баскетбол</option><option>Бокс</option><option>Борьба вольная</option><option>Борьба греко-римская</option><option>Велоспорт</option><option>Водное поло</option><option>Волейбол</option><option>Гандбол</option><option>Гимнастика спортивная</option><option>Гимнастика художественная</option><option>Гольф</option><option>Гребля на байдарках и каноэ</option><option>Дзюдо</option><option>Карате</option><option>Конный спорт</option><option>Лёгкая атлетика</option><option>Настольный теннис</option><option>Парусный спорт</option><option>Плавание</option><option>Прыжки в воду</option><option>Прыжки на батуте</option><option>Регби</option><option>Сёрфинг</option><option>Синхронное плавание</option><option>Скейтбординг</option><option>Современное пятиборье</option><option>Спортивное скалолазание</option><option>Стрельба из лука</option><option>Стрельба пулевая</option><option>Теннис</option><option>Триатлон</option><option>Тхэквондо</option><option>Тяжёлая атлетика</option><option>Фехтование</option><option>Футбол</option><option>Хоккей на траве</option></select>
|
||
<div class="reg-nav">
|
||
<button class="btn outline" onclick="prevStep()">← Назад</button>
|
||
<button class="btn" onclick="nextStep()">Дальше →</button>
|
||
</div>
|
||
</div>
|
||
<!-- Step 4: Birth & Avatar -->
|
||
<div class="reg-step" data-step="4">
|
||
<h2>🎂 Дата рождения</h2>
|
||
<p class="hint">Возраст посчитается автоматически</p>
|
||
<input type="date" id="rBirth" onchange="autoAge()">
|
||
<input type="text" id="rAge" placeholder="Возраст (авто)" readonly style="background:#0F1218;border-color:#1a2332">
|
||
<p style="text-align:center;color:var(--gray-500);font-size:13px;margin-top:8px">📷 Выбери аватарку:</p>
|
||
<div class="avatar-picker" id="avatarPicker">
|
||
<div class="avatar-opt selected" data-emoji="🏊">🏊</div>
|
||
<div class="avatar-opt" data-emoji="🏃">🏃</div>
|
||
<div class="avatar-opt" data-emoji="🏋">🏋</div>
|
||
<div class="avatar-opt" data-emoji="⚽">⚽</div>
|
||
<div class="avatar-opt" data-emoji="🏀">🏀</div>
|
||
<div class="avatar-opt" data-emoji="🥊">🥊</div>
|
||
<div class="avatar-opt" data-emoji="🏈">🏈</div>
|
||
<div class="avatar-opt" data-emoji="🎾">🎾</div>
|
||
</div>
|
||
<label class="file-btn" style="text-align:center;width:100%">📷 Или загрузи своё фото<input type="file" accept="image/*" id="rPhotoFile" onchange="previewRegPhoto()"></label>
|
||
<img id="rPhotoPreview" style="width:64px;height:64px;border-radius:50%;object-fit:cover;display:none;margin:0 auto">
|
||
<div class="reg-nav">
|
||
<button class="btn outline" onclick="prevStep()">← Назад</button>
|
||
<button class="btn" onclick="nextStep()">Дальше →</button>
|
||
</div>
|
||
</div>
|
||
<!-- Step 5: Location -->
|
||
<div class="reg-step" data-step="5">
|
||
<h2>🌍 Где ты?</h2>
|
||
<p class="hint">Страна и город</p>
|
||
<select id="rCountry" onchange="updateCities()"><option value="">Страна</option><option>Австралия</option><option>Австрия</option><option>Азербайджан</option><option>Армения</option><option>Беларусь</option><option>Бельгия</option><option>Болгария</option><option>Бразилия</option><option>Великобритания</option><option>Венгрия</option><option>Германия</option><option>Греция</option><option>Грузия</option><option>Дания</option><option>Египет</option><option>Израиль</option><option>Индия</option><option>Индонезия</option><option>Испания</option><option>Италия</option><option>Казахстан</option><option>Канада</option><option>Катар</option><option>Китай</option><option>Корея Южная</option><option>Куба</option><option>Кыргызстан</option><option>Латвия</option><option>Литва</option><option>Малайзия</option><option>Мексика</option><option>Молдова</option><option>Монголия</option><option>Нидерланды</option><option>Новая Зеландия</option><option>Норвегия</option><option>ОАЭ</option><option>Польша</option><option>Португалия</option><option>Россия</option><option>Румыния</option><option>Саудовская Аравия</option><option>Сербия</option><option>Сингапур</option><option>США</option><option>Таджикистан</option><option>Таиланд</option><option>Туркменистан</option><option>Турция</option><option>Узбекистан</option><option>Украина</option><option>Финляндия</option><option>Франция</option><option>Хорватия</option><option>Чехия</option><option>Швейцария</option><option>Швеция</option><option>Эстония</option><option>ЮАР</option><option>Япония</option></select>
|
||
<input type="text" id="rCity" placeholder="Город" list="cityList" autocomplete="off">
|
||
<datalist id="cityList"></datalist>
|
||
<div class="reg-nav">
|
||
<button class="btn outline" onclick="prevStep()">← Назад</button>
|
||
<button class="btn" onclick="nextStep()">Дальше →</button>
|
||
</div>
|
||
</div>
|
||
<!-- Step 6: Club & Coach -->
|
||
<div class="reg-step" data-step="6">
|
||
<h2>🏫 Клуб и тренер</h2>
|
||
<p class="hint">Где и с кем ты тренируешься</p>
|
||
<input type="text" id="rClub" placeholder="Клуб / спортивная школа">
|
||
<input type="text" id="rCoach" placeholder="Тренер (ФИО)">
|
||
<input type="text" id="rRank" placeholder="Разряд / звание (например: 1 юн, 3 взр)">
|
||
<input type="text" id="rGoal" placeholder="Твоя главная цель (например: 50 м в/с за 23″)">
|
||
<div class="reg-nav">
|
||
<button class="btn outline" onclick="prevStep()">← Назад</button>
|
||
<button class="btn" onclick="nextStep()">Дальше →</button>
|
||
</div>
|
||
</div>
|
||
<!-- Step 7: Contacts -->
|
||
<div class="reg-step" data-step="7">
|
||
<h2>📱 Контакты</h2>
|
||
<p class="hint">Телефон и email — чтобы тренер и родители могли связаться</p>
|
||
<input type="tel" id="rPhone" placeholder="Телефон (WhatsApp)">
|
||
<input type="email" id="rEmail" placeholder="Email">
|
||
<div class="reg-nav">
|
||
<button class="btn outline" onclick="prevStep()">← Назад</button>
|
||
<button class="btn" onclick="finishReg()">✅ Завершить!</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MAIN APP SCREEN -->
|
||
<div class="screen" id="appScreen">
|
||
<div class="content" id="mainContent"></div>
|
||
<div class="bottom-nav">
|
||
<button class="nav-item active" data-page="profile" onclick="showPage('profile')"><span class="icon">👤</span>Профиль</button>
|
||
<button class="nav-item" data-page="diary" onclick="showPage('diary')"><span class="icon">📖</span>Дневник</button>
|
||
<button class="nav-item" data-page="chat" onclick="showPage('chat')"><span class="icon">💬</span>Чаты</button>
|
||
<button class="nav-item" data-page="tools" onclick="showPage('tools')"><span class="icon">⚙</span>Инструменты</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toast"></div>
|
||
<div class="lightbox" id="lightbox" style="display:none" onclick="this.style.display='none'"></div>
|
||
|
||
<script>
|
||
// === DATA ===
|
||
const LS=(k)=>{try{return JSON.parse(localStorage.getItem('g_'+k))}catch{return null}};
|
||
const SS=(k,v)=>{try{localStorage.setItem('g_'+k,JSON.stringify(v))}catch{toast('Память полна!')}};
|
||
let currentUser=null, currentPage='profile', currentChat=null, chatFilter='all';
|
||
|
||
function toast(m){const t=document.getElementById('toast');t.textContent=m;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2000)}
|
||
|
||
// === LOGIN ===
|
||
function doLogin(){
|
||
const u=document.getElementById('lUser').value.trim().toLowerCase();
|
||
const p=document.getElementById('lPass').value;
|
||
if(!u||!p){showErr('Введи логин и пароль');return}
|
||
const users=LS('users')||[];
|
||
const user=users.find(x=>x.login===u&&x.pass===p);
|
||
if(!user){showErr('Неверный логин или пароль');return}
|
||
currentUser=user;
|
||
document.getElementById('loginScreen').classList.remove('active');
|
||
document.getElementById('appScreen').classList.add('active');
|
||
hideErr();
|
||
renderAll();
|
||
}
|
||
function showErr(m){const e=document.getElementById('loginErr');e.textContent=m;e.style.display='block'}
|
||
function hideErr(){document.getElementById('loginErr').style.display='none'}
|
||
|
||
// === REGISTRATION ===
|
||
let regStep=1, regPhoto=null, regAvatar='🏊';
|
||
|
||
function startReg(){
|
||
document.getElementById('loginScreen').classList.remove('active');
|
||
document.getElementById('regScreen').classList.add('active');
|
||
regStep=1; showRegStep(1);
|
||
buildStepDots();
|
||
}
|
||
function backToLogin(){
|
||
document.getElementById('regScreen').classList.remove('active');
|
||
document.getElementById('loginScreen').classList.add('active');
|
||
}
|
||
function showRegStep(n){
|
||
regStep=n;
|
||
document.querySelectorAll('.reg-step').forEach(s=>s.classList.remove('active'));
|
||
const step=document.querySelector(`.reg-step[data-step="${n}"]`);
|
||
if(step){step.classList.add('active');step.querySelector('input,select')?.focus()}
|
||
document.getElementById('stepNum').textContent=n;
|
||
updateStepDots();
|
||
}
|
||
function buildStepDots(){
|
||
let h=''; for(let i=1;i<=7;i++)h+=`<div class="step-dot${i<=regStep?' done':''}${i===regStep?' current':''}"></div>`;
|
||
document.getElementById('stepDots').innerHTML=h;
|
||
}
|
||
function updateStepDots(){
|
||
document.querySelectorAll('#stepDots .step-dot').forEach((d,i)=>{
|
||
d.classList.toggle('done',i+1<=regStep);
|
||
d.classList.toggle('current',i+1===regStep);
|
||
});
|
||
}
|
||
function nextStep(){
|
||
if(regStep===1&&!document.getElementById('rName').value.trim()){toast('Введи ФИО!');return}
|
||
if(regStep===2){const l=document.getElementById('rLogin').value.trim();const p=document.getElementById('rPass').value;if(!l){toast('Придумай логин!');return}if(!p||p.length<3){toast('Пароль — минимум 3 символа!');return}const users=LS('users')||[];if(users.find(x=>x.login===l.toLowerCase())){toast('Такой логин уже занят!');return}}
|
||
if(regStep===3&&!document.getElementById('rSport').value){toast('Выбери вид спорта!');return}
|
||
if(regStep<7){buildStepDots();showRegStep(regStep+1)}else finishReg()
|
||
}
|
||
function prevStep(){if(regStep>1)showRegStep(regStep-1);buildStepDots()}
|
||
function autoAge(){const b=document.getElementById('rBirth').value;if(!b)return;const bd=new Date(b),td=new Date();let a=td.getFullYear()-bd.getFullYear();const m=td.getMonth()-bd.getMonth();if(m<0||(m===0&&td.getDate()<bd.getDate()))a--;document.getElementById('rAge').value=a}
|
||
function previewRegPhoto(){const f=document.getElementById('rPhotoFile').files[0];if(!f)return;const r=new FileReader();r.onload=e=>{regPhoto=e.target.result;const p=document.getElementById('rPhotoPreview');p.src=e.target.result;p.style.display='block'};r.readAsDataURL(f)}
|
||
document.querySelectorAll('.avatar-opt').forEach(a=>{a.addEventListener('click',()=>{document.querySelectorAll('.avatar-opt').forEach(x=>x.classList.remove('selected'));a.classList.add('selected');regAvatar=a.dataset.emoji;regPhoto=null;document.getElementById('rPhotoPreview').style.display='none'})});
|
||
|
||
function finishReg(){
|
||
const name=document.getElementById('rName').value.trim();
|
||
const login=document.getElementById('rLogin').value.trim().toLowerCase();
|
||
const pass=document.getElementById('rPass').value;
|
||
const sport=document.getElementById('rSport').value;
|
||
const birth=document.getElementById('rBirth').value;
|
||
autoAge();
|
||
const age=document.getElementById('rAge').value;
|
||
const country=document.getElementById('rCountry').value;
|
||
const city=document.getElementById('rCity').value.trim();
|
||
const club=document.getElementById('rClub').value.trim();
|
||
const coach=document.getElementById('rCoach').value.trim();
|
||
const rank=document.getElementById('rRank').value.trim();
|
||
const goal=document.getElementById('rGoal').value.trim();
|
||
const phone=document.getElementById('rPhone').value.trim();
|
||
const email=document.getElementById('rEmail').value.trim();
|
||
|
||
if(!name||!login||!pass||!sport){toast('Заполни обязательные поля!');return}
|
||
|
||
const user={
|
||
id:Date.now(),name,login,pass,sport,birth,age,country,city,club,coach,rank,goal,phone,email,
|
||
avatar:regPhoto||regAvatar,photo:regPhoto||null,
|
||
created:new Date().toISOString(),
|
||
achievements:[]
|
||
};
|
||
const users=LS('users')||[];
|
||
if(users.find(x=>x.login===login)){toast('Логин занят!');return}
|
||
users.push(user);
|
||
SS('users',users);
|
||
currentUser=user;
|
||
document.getElementById('regScreen').classList.remove('active');
|
||
document.getElementById('appScreen').classList.add('active');
|
||
// reset form
|
||
regStep=1;regPhoto=null;regAvatar='🏊';
|
||
['rName','rLogin','rPass','rSport','rBirth','rAge','rCountry','rCity','rClub','rCoach','rRank','rGoal','rPhone','rEmail'].forEach(id=>{const el=document.getElementById(id);if(el){if(el.tagName==='SELECT')el.selectedIndex=0;else el.value=''}});
|
||
document.getElementById('rPhotoPreview').style.display='none';
|
||
renderAll();
|
||
toast('🎉 Профиль создан! Добро пожаловать в Галикон!');
|
||
}
|
||
|
||
// === USER STORAGE ===
|
||
function uid(){return currentUser?currentUser.id:'_'}
|
||
function getMy(k){const all=LS(k)||{};return all[uid()]||null}
|
||
function setMy(k,v){const all=LS(k)||{};all[uid()]=v;SS(k,all)}
|
||
function getMyArr(k){const all=LS(k)||{};return all[uid()]||[]}
|
||
function setMyArr(k,v){const all=LS(k)||{};all[uid()]=v;SS(k,all)}
|
||
|
||
// === PAGES ===
|
||
function showPage(page){
|
||
currentPage=page;
|
||
document.querySelectorAll('.nav-item').forEach(n=>n.classList.toggle('active',n.dataset.page===page));
|
||
renderPage();
|
||
}
|
||
function renderPage(){
|
||
const c=document.getElementById('mainContent');
|
||
switch(currentPage){
|
||
case 'profile':c.innerHTML=renderProfile();break;
|
||
case 'diary':c.innerHTML=renderDiaryPage();break;
|
||
case 'chat':c.innerHTML=renderChatPage();currentChat=null;break;
|
||
case 'tools':c.innerHTML=renderToolsPage();break;
|
||
}
|
||
}
|
||
|
||
// === PROFILE ===
|
||
function renderProfile(){
|
||
const u=currentUser;
|
||
const av=u.photo?(u.photo.startsWith('data:')?u.photo:u.avatar):u.avatar;
|
||
const h=`
|
||
<div class="profile-header">
|
||
<div class="avatar">${u.photo?`<img src="${u.photo}">`:av}</div>
|
||
<h2>${u.name}</h2>
|
||
<div class="tag">${u.sport}</div>
|
||
${u.rank?`<div style="margin-top:4px;font-size:13px;color:var(--gray-500)">${u.rank}</div>`:''}
|
||
${u.goal?`<div style="margin-top:4px;font-size:14px;color:var(--cyan)">🎯 ${u.goal}</div>`:''}
|
||
</div>
|
||
<div class="card">
|
||
<h3>📋 Информация</h3>
|
||
<div style="font-size:14px;line-height:2">
|
||
${u.birth?`🎂 ${u.birth} (${u.age||'?'} лет)<br>`:''}
|
||
${u.country?`🌍 ${u.country}${u.city?', '+u.city:''}<br>`:''}
|
||
${u.club?`🏫 ${u.club}<br>`:''}
|
||
${u.coach?`👨‍🏫 Тренер: ${u.coach}<br>`:''}
|
||
${u.phone?`📱 ${u.phone}<br>`:''}
|
||
${u.email?`✉️ ${u.email}<br>`:''}
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h3>🏆 Достижения</h3>
|
||
${u.achievements&&u.achievements.length?u.achievements.map(a=>`<div style="padding:8px 0;border-bottom:1px solid #2a3342;font-size:14px"><strong>${a.title}</strong><br><span style="color:var(--gray-500);font-size:13px">${a.date} · ${a.desc||''}</span></div>`).join(''):'<p class="muted">Пока нет достижений. Добавь первое!</p>'}
|
||
<button class="btn small outline" onclick="addAchievement()" style="margin-top:8px">+ Добавить</button>
|
||
<div id="achForm" style="display:none;margin-top:8px">
|
||
<input type="text" id="achTitle" placeholder="Название (например: 1 место на Весёлый Дельфин)">
|
||
<input type="date" id="achDate">
|
||
<input type="text" id="achDesc" placeholder="Описание (результат, дистанция...)">
|
||
<button class="btn small" onclick="saveAchievement()">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
<button class="btn danger" onclick="doLogout()" style="margin-top:8px">🚪 Выйти</button>
|
||
`;return h;
|
||
}
|
||
function addAchievement(){document.getElementById('achForm').style.display='block'}
|
||
function saveAchievement(){
|
||
const t=document.getElementById('achTitle').value.trim();
|
||
const d=document.getElementById('achDate').value;
|
||
const desc=document.getElementById('achDesc').value.trim();
|
||
if(!t||!d){toast('Введи название и дату!');return}
|
||
const u=currentUser;
|
||
if(!u.achievements)u.achievements=[];
|
||
u.achievements.unshift({title:t,date:d,desc});
|
||
const users=LS('users')||[];
|
||
const idx=users.findIndex(x=>x.id===u.id);
|
||
if(idx>=0){users[idx]=u;SS('users',users);currentUser=u}
|
||
document.getElementById('achTitle').value='';document.getElementById('achDate').value='';document.getElementById('achDesc').value='';
|
||
document.getElementById('achForm').style.display='none';
|
||
renderPage();toast('Достижение добавлено!');
|
||
}
|
||
|
||
// === DIARY ===
|
||
function renderDiaryPage(){
|
||
const diary=getMyArr('diary');
|
||
const today=new Date().toISOString().slice(0,10);
|
||
let h=`
|
||
<div class="card">
|
||
<h3>✏ Новая запись</h3>
|
||
<input type="date" id="dDate" value="${today}">
|
||
<select id="dType"><option>Скорость</option><option>Техника</option><option>Выносливость</option><option>ОФП</option><option>Соревнование</option></select>
|
||
<input type="number" id="dKm" placeholder="Километраж" step="0.1">
|
||
<input type="text" id="dTime" placeholder="Лучшее время">
|
||
<input type="number" id="dFeel" placeholder="Самочувствие (1-5)" min="1" max="5">
|
||
<input type="text" id="dNote" placeholder="Заметка">
|
||
<button class="btn" onclick="addDiary()">Сохранить</button>
|
||
</div>`;
|
||
if(!diary.length){h+='<div class="empty-state"><div class="big">💭</div>Пока нет записей</div>'}
|
||
else{
|
||
h+=diary.slice(0,20).map(e=>`
|
||
<div class="card" style="padding:14px">
|
||
<div style="display:flex;justify-content:space-between"><strong>${e.date}</strong><span class="badge blue">${e.type}</span></div>
|
||
<div class="muted">📏 ${e.km} км | ⏱ ${e.time} | 🌟 ${e.feel}/5${e.note!=='—'?'<br>'+e.note:''}</div>
|
||
<button class="btn danger small" style="margin-top:4px" onclick="delDiary(${e.id})">Удалить</button>
|
||
</div>`).join('');
|
||
}
|
||
return h;
|
||
}
|
||
function addDiary(){
|
||
const e={id:Date.now(),date:document.getElementById('dDate').value,type:document.getElementById('dType').value,km:document.getElementById('dKm').value||'0',time:document.getElementById('dTime').value||'—',feel:document.getElementById('dFeel').value||'—',note:document.getElementById('dNote').value||'—'};
|
||
const d=getMyArr('diary');d.unshift(e);setMyArr('diary',d);
|
||
renderPage();toast('Записано!');
|
||
}
|
||
function delDiary(id){const d=getMyArr('diary').filter(x=>x.id!==id);setMyArr('diary',d);renderPage();toast('Удалено')}
|
||
|
||
// === CHAT ===
|
||
function renderChatPage(){
|
||
if(currentChat)return renderChatView();
|
||
return renderChatList();
|
||
}
|
||
function renderChatList(){
|
||
const users=LS('users')||[];
|
||
const other=users.filter(u=>u.id!==uid());
|
||
const msgs=LS('messages')||{};
|
||
let h='<div class="card"><div class="chat-tabs"><button class="chat-tab active" onclick="chatFilter=\'all\';renderPage()">💬 Все</button><button class="chat-tab" onclick="chatFilter=\'athlete\';renderPage()">🏊 Спортсмены</button><button class="chat-tab" onclick="chatFilter=\'coach\';renderPage()">🏋 Тренеры</button><button class="chat-tab" onclick="chatFilter=\'parent\';renderPage()">👨‍👦 Родители</button></div>';
|
||
// Games button
|
||
h+='<button class="btn outline" onclick="renderGames()" style="width:100%;margin-bottom:8px">🎮 Игры</button>';
|
||
Show create group chat button
|
||
|
||
h+='<button class="btn small outline" onclick="createGroupChat()" style="width:100%;margin-bottom:8px">+ Создать групповой чат</button>';
|
||
if(!other.length){h+='<div class="empty-state"><div class="big">💬</div>Нет других пользователей</div></div>';return h}
|
||
h+='<div style="font-size:13px;color:var(--gray-500);margin-bottom:6px">Выбери с кем общаться:</div>';
|
||
other.forEach(u=>{
|
||
const chatKey=[uid(),u.id].sort().join('_');
|
||
const chatMsgs=msgs[chatKey]||[];
|
||
const last=chatMsgs[chatMsgs.length-1];
|
||
const unread=chatMsgs.filter(m=>m.to===uid()&&!m.read).length;
|
||
h+=`<div class="chat-list-item" onclick="openChat(${u.id})">
|
||
<div class="av">${u.photo?`<img src="${u.photo}" style="width:100%;height:100%;border-radius:50%;object-fit:cover">`:u.avatar||u.name.charAt(0)}</div>
|
||
<div class="info"><div class="name">${u.name}${u.role?` <span class="badge blue" style="font-size:9px">${u.role}</span>`:''}</div><div class="last">${last?last.text:'Начни общение'}</div></div>
|
||
${unread?`<span class="unread">${unread}</span>`:''}
|
||
</div>`;
|
||
});
|
||
h+='</div>';return h;
|
||
}
|
||
function openChat(id){
|
||
currentChat=id;
|
||
// Mark messages as read
|
||
const msgs=LS('messages')||{};
|
||
const chatKey=[uid(),id].sort().join('_');
|
||
if(msgs[chatKey])msgs[chatKey].forEach(m=>{if(m.to===uid())m.read=true});
|
||
SS('messages',msgs);
|
||
renderPage();
|
||
}
|
||
function renderChatView(){
|
||
const other=((LS('users')||[]).find(u=>u.id===currentChat));
|
||
const chatKey=[uid(),currentChat].sort().join('_');
|
||
const msgs=(LS('messages')||{})[chatKey]||[];
|
||
let h=`<div style="display:flex;align-items:center;gap:8px;padding:8px 0">
|
||
<button class="btn small outline" onclick="currentChat=null;renderPage()">←</button>
|
||
<strong>${other?other.name:'Чат'}</strong>
|
||
</div>
|
||
<div class="chat-messages" id="chatMsgs">`;
|
||
if(!msgs.length)h+='<div class="empty-state"><div class="big">💬</div>Начните общение!</div>';
|
||
else msgs.forEach(m=>{h+=`<div class="chat-msg ${m.from===uid()?'mine':'theirs'}">${m.from!==uid()?`<div class="sender">${other?other.name:'Пользователь'}</div>`:''}${m.text}<div style="font-size:10px;color:${m.from===uid()?'var(--ink)':'var(--gray-500)'};margin-top:2px">${m.time||''}</div></div>`});
|
||
h+='</div><div class="chat-input-row"><input type="text" id="msgInput" placeholder="Сообщение..." onkeydown="if(event.key===\'Enter\')sendMsg()"><button class="btn small" onclick="sendMsg()">➤</button></div>';
|
||
return h;
|
||
}
|
||
function sendMsg(){
|
||
const text=document.getElementById('msgInput')?.value.trim();
|
||
if(!text||!currentChat)return;
|
||
const msgs=LS('messages')||{};
|
||
const chatKey=[uid(),currentChat].sort().join('_');
|
||
if(!msgs[chatKey])msgs[chatKey]=[];
|
||
msgs[chatKey].push({from:uid(),to:currentChat,text,time:new Date().toLocaleTimeString('ru-RU',{hour:'2-digit',minute:'2-digit'}),read:false});
|
||
SS('messages',msgs);
|
||
document.getElementById('msgInput').value='';
|
||
renderPage();
|
||
// scroll to bottom
|
||
setTimeout(()=>{const el=document.getElementById('chatMsgs');if(el)el.scrollTop=el.scrollHeight},100);
|
||
}
|
||
let gameState = null;
|
||
|
||
function renderGames(){
|
||
const c = document.getElementById('mainContent');
|
||
c.innerHTML = `
|
||
<div class="card" style="text-align:center">
|
||
<h3>🎮 Игры в чате</h3>
|
||
<p class="muted">Выбери игру и играй с друзьями!</p>
|
||
<button class="quiz-option" onclick="startTicTacToe()">❌⭕ Крестики-нолики</button>
|
||
<button class="quiz-option" onclick="startGuessNumber()">🔢 Угадай число (1-100)</button>
|
||
<button class="quiz-option" onclick="startReaction()">⚡ Реакция — кто быстрее</button>
|
||
<button class="quiz-option" onclick="startQuiz()">🏆 Спорт-викторина</button>
|
||
<button class="btn small outline" onclick="showPage('chat')" style="margin-top:8px">← Назад в чаты</button>
|
||
</div>
|
||
<div id="gameArea"></div>
|
||
`;
|
||
}
|
||
|
||
function startTicTacToe(){
|
||
gameState = { type: 'tictactoe', board: Array(9).fill(''), turn: 'X', over: false };
|
||
renderTicTacToe();
|
||
}
|
||
function renderTicTacToe(){
|
||
const g = gameState;
|
||
let h = '<div class="card"><h3 style="text-align:center">❌⭕ Крестики-нолики</h3><div class="game-status">';
|
||
if(g.over){
|
||
h += g.winner === 'draw' ? 'Ничья!' : g.winner + ' победил!';
|
||
h += '<br><button class="btn small outline" onclick="startTicTacToe()" style="margin-top:8px">Играть ещё</button>';
|
||
} else {
|
||
h += 'Ход: ' + (g.turn === 'X' ? '❌ Крестики' : '⭕ Нолики');
|
||
}
|
||
h += '</div><div class="game-grid">';
|
||
g.board.forEach((cell, i) => {
|
||
h += `<div class="game-cell${cell?' '+ (cell==='X'?'x':'o')+(g.over||cell?' disabled':'') :''}" onclick="ticMove(${i})">${cell}</div>`;
|
||
});
|
||
h += '</div><button class="btn small outline" onclick="startTicTacToe()" style="width:100%;margin-top:8px">Новая игра</button></div>';
|
||
document.getElementById('gameArea').innerHTML = h;
|
||
}
|
||
function ticMove(i){
|
||
if(!gameState||gameState.over||gameState.board[i])return;
|
||
gameState.board[i] = gameState.turn;
|
||
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
|
||
for(const [a,b,c] of lines){
|
||
if(gameState.board[a]&&gameState.board[a]===gameState.board[b]&&gameState.board[a]===gameState.board[c]){
|
||
gameState.over = true; gameState.winner = gameState.board[a]; break;
|
||
}
|
||
}
|
||
if(!gameState.over && gameState.board.every(c=>c)){ gameState.over = true; gameState.winner = 'draw'; }
|
||
if(!gameState.over) gameState.turn = gameState.turn === 'X' ? 'O' : 'X';
|
||
renderTicTacToe();
|
||
}
|
||
|
||
function startGuessNumber(){
|
||
gameState = { type: 'guess', number: Math.floor(Math.random()*100)+1, attempts: 0, max: 7 };
|
||
renderGuessNumber();
|
||
}
|
||
function renderGuessNumber(){
|
||
const g = gameState;
|
||
let h = `<div class="card" style="text-align:center"><h3>🔢 Угадай число (1-100)</h3>
|
||
<p class="muted">Осталось попыток: <strong>${g.max - g.attempts}</strong></p>`;
|
||
if(g.won){
|
||
h += `<p style="color:var(--green);font-size:20px;font-weight:800">🎉 Угадал! Это ${g.number}!</p><p class="muted">За ${g.attempts} попыток</p>`;
|
||
h += '<button class="btn small outline" onclick="startGuessNumber()">Играть ещё</button>';
|
||
} else if(g.attempts >= g.max){
|
||
h += `<p style="color:var(--red);font-size:18px;font-weight:800">Не угадал! Это было ${g.number}</p>`;
|
||
h += '<button class="btn small outline" onclick="startGuessNumber()">Играть ещё</button>';
|
||
} else {
|
||
h += '<input type="number" id="guessInput" placeholder="Твоё число" min="1" max="100" onkeydown="if(event.key==='Enter')doGuess()"><button class="btn" onclick="doGuess()" style="margin-top:8px">Проверить</button>';
|
||
}
|
||
h += '</div><button class="btn small outline" onclick="renderGames()" style="width:100%">← К играм</button>';
|
||
document.getElementById('gameArea').innerHTML = h;
|
||
}
|
||
function doGuess(){
|
||
if(!gameState||gameState.won||gameState.attempts>=gameState.max)return;
|
||
const n = +document.getElementById('guessInput').value;
|
||
if(!n||n<1||n>100)return;
|
||
gameState.attempts++;
|
||
if(n === gameState.number){ gameState.won = true; renderGuessNumber(); return }
|
||
const hint = n < gameState.number ? '📈 Больше!' : '📉 Меньше!';
|
||
const tempDiv = document.createElement('div'); tempDiv.innerHTML = `<div class="card" style="text-align:center;padding:12px;margin-bottom:8px"><p>${n} — ${hint}</p></div>`;
|
||
document.getElementById('gameArea').insertBefore(tempDiv, document.getElementById('gameArea').firstChild);
|
||
if(gameState.attempts >= gameState.max) renderGuessNumber();
|
||
else { document.getElementById('guessInput').value=''; document.getElementById('guessInput').focus(); }
|
||
}
|
||
|
||
function startReaction(){
|
||
gameState = { type: 'reaction', state: 'waiting', startTime: 0, best: getMy('reactionBest') || 9999 };
|
||
renderReaction();
|
||
}
|
||
function renderReaction(){
|
||
const g = gameState;
|
||
let bg = '#151c28', txt = '';
|
||
if(g.state === 'waiting'){ bg = '#FF6B6B'; txt = '🔴 Жди зелёный!'; }
|
||
else if(g.state === 'ready'){ bg = '#4CAF50'; txt = '🟢 ЖМИ СЕЙЧАС!'; }
|
||
else if(g.state === 'early'){ bg = '#FFD700'; txt = '⚠ Слишком рано! Жми "Начать"'; }
|
||
else if(g.state === 'done'){ txt = `⏱ Твоё время: <strong>${g.reaction} мс</strong><br>Рекорд: ${g.best !== 9999 ? g.best+' мс' : '—'}`; }
|
||
let h = `<div class="card" style="text-align:center">
|
||
<h3>⚡ Реакция</h3>
|
||
<div style="width:200px;height:200px;border-radius:50%;background:${bg};display:flex;align-items:center;justify-content:center;margin:16px auto;cursor:${g.state==='ready'?'pointer':'default'};font-size:18px;font-weight:800;transition:background .1s" onclick="reactClick()">${txt||'🎯 Нажми "Начать"'}</div>`;
|
||
if(g.state === 'done'){
|
||
h += '<button class="btn small outline" onclick="startReaction()">Ещё раз</button>';
|
||
} else if(g.state === 'waiting' || g.state === 'early'){
|
||
h += '<button class="btn" onclick="reactStart()">Начать</button>';
|
||
}
|
||
h += '</div><button class="btn small outline" onclick="renderGames()" style="width:100%">← К играм</button>';
|
||
document.getElementById('gameArea').innerHTML = h;
|
||
}
|
||
function reactStart(){
|
||
if(!gameState||gameState.state==='ready')return;
|
||
gameState.state = 'waiting'; renderReaction();
|
||
const delay = 1500 + Math.random() * 3000;
|
||
setTimeout(() => {
|
||
if(gameState.state !== 'waiting') return;
|
||
gameState.state = 'ready'; gameState.startTime = Date.now(); renderReaction();
|
||
}, delay);
|
||
}
|
||
function reactClick(){
|
||
if(!gameState)return;
|
||
if(gameState.state === 'waiting'){ gameState.state = 'early'; renderReaction(); return; }
|
||
if(gameState.state === 'ready'){ gameState.reaction = Date.now() - gameState.startTime; gameState.state = 'done';
|
||
if(gameState.reaction < gameState.best){ gameState.best = gameState.reaction; setMy('reactionBest', gameState.reaction); }
|
||
renderReaction(); }
|
||
}
|
||
|
||
function startQuiz(){
|
||
const questions = [
|
||
{q:'Сколько метров в олимпийском бассейне?',a:['25 м','50 м','100 м','33 м'],r:1},
|
||
{q:'Какой стиль плавания самый быстрый?',a:['Брасс','Баттерфляй','Кроль','На спине'],r:2},
|
||
{q:'Сколько золотых медалей у Майкла Фелпса?',a:['8','15','23','28'],r:2},
|
||
{q:'Как зовут лучшего спринтера мира (50 м в/с)?',a:['Майкл Фелпс','Калеб Дрессел','Райан Лохте','Адам Пити'],r:1},
|
||
{q:'Где пройдёт Олимпиада-2032?',a:['Лос-Анджелес','Париж','Брисбен','Токио'],r:2},
|
||
{q:'Сколько длится олимпийский цикл?',a:['2 года','3 года','4 года','5 лет'],r:2},
|
||
{q:'Какая страна выиграла больше всех медалей в плавании?',a:['Китай','Австралия','Россия','США'],r:3},
|
||
{q:'Как называется поворот в кроле?',a:['Сальто','Кувырок','Разворот','Маятник'],r:1}
|
||
];
|
||
gameState = { type: 'quiz', questions, current: 0, score: 0, answered: false };
|
||
renderQuiz();
|
||
}
|
||
function renderQuiz(){
|
||
const g = gameState;
|
||
if(g.current >= g.questions.length){
|
||
document.getElementById('gameArea').innerHTML = `<div class="card" style="text-align:center"><h3>🏆 Викторина завершена!</h3><p style="font-size:24px;font-weight:800;color:var(--cyan)">${g.score} / ${g.questions.length}</p><p class="muted">${g.score>=6?'👑 Ты знаток спорта!':g.score>=4?'👍 Неплохо!':'📚 Учи матчасть!'}</p><button class="btn" onclick="startQuiz()">Ещё раз</button></div>`;
|
||
return;
|
||
}
|
||
const q = g.questions[g.current];
|
||
let h = `<div class="card"><h3>❓ Вопрос ${g.current+1}/${g.questions.length}</h3><p style="font-size:17px;font-weight:600;margin:12px 0">${q.q}</p>`;
|
||
q.a.forEach((ans,i)=>{
|
||
let cls = 'quiz-option';
|
||
if(g.answered){
|
||
if(i === q.r) cls += ' right';
|
||
else if(g.selected === i) cls += ' wrong';
|
||
}
|
||
h += `<button class="${cls}" onclick="answerQuiz(${i})" ${g.answered?'disabled':''}>${ans}</button>`;
|
||
});
|
||
h += `<div style="margin-top:8px;font-size:13px;color:var(--gray-500)">Счёт: ${g.score}</div>`;
|
||
if(g.answered){
|
||
h += '<button class="btn small" onclick="nextQuiz()" style="margin-top:8px">Дальше →</button>';
|
||
}
|
||
h += '</div>';
|
||
document.getElementById('gameArea').innerHTML = h;
|
||
}
|
||
function answerQuiz(i){
|
||
if(!gameState||gameState.answered)return;
|
||
gameState.answered = true; gameState.selected = i;
|
||
if(i === gameState.questions[gameState.current].r) gameState.score++;
|
||
renderQuiz();
|
||
}
|
||
function nextQuiz(){
|
||
gameState.current++; gameState.answered = false; gameState.selected = null; renderQuiz();
|
||
}
|
||
|
||
|
||
function createGroupChat(){
|
||
const name=prompt('Название группового чата:');
|
||
if(!name)return;
|
||
const groups=LS('groups')||[];
|
||
groups.push({id:Date.now(),name,members:[uid()],messages:[]});
|
||
SS('groups',groups);
|
||
toast('Групповой чат создан!');
|
||
renderPage();
|
||
}
|
||
|
||
// === TOOLS PAGE ===
|
||
function renderToolsPage(){
|
||
return `
|
||
<div class="card">
|
||
<h3>📊 Нормативы (плавание, 50 м бассейн)</h3>
|
||
<table><tr><th>Разряд</th><th>50 м в/с</th><th>100 м в/с</th><th>400 м в/с</th></tr>
|
||
<tr><td><span class="badge gold">МСМК</span></td><td>21.50</td><td>47.00</td><td>3:48</td></tr>
|
||
<tr><td><span class="badge blue">МС</span></td><td>23.00</td><td>50.50</td><td>4:05</td></tr>
|
||
<tr><td><span class="badge green">КМС</span></td><td>25.00</td><td>54.50</td><td>4:25</td></tr>
|
||
<tr><td>1 взр.</td><td>28.00</td><td>1:01</td><td>5:00</td></tr>
|
||
<tr><td>2 взр.</td><td>31.50</td><td>1:09</td><td>5:40</td></tr>
|
||
<tr><td>3 взр.</td><td>35.00</td><td>1:17</td><td>6:20</td></tr>
|
||
<tr><td>1 юн.</td><td>40.00</td><td>1:29</td><td>7:10</td></tr>
|
||
<tr><td>2 юн.</td><td>47.00</td><td>1:45</td><td>8:10</td></tr></table>
|
||
</div>
|
||
<div class="card">
|
||
<h3>💊 Здоровье</h3>
|
||
<p class="muted" style="margin-bottom:8px">Отмечай принятые витамины:</p>
|
||
<div id="vitCheck"></div>
|
||
<button class="btn small outline" onclick="addSleepEntry()" style="margin-top:8px">+ Записать сон и пульс</button>
|
||
<div id="vitHistory" class="muted" style="margin-top:8px"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h3>📚 Видеоуроки</h3>
|
||
<p class="muted">Найди на YouTube:</p>
|
||
${['swimming start technique','flip turn tutorial','freestyle stroke technique','underwater dolphin kick','breaststroke technique','Caeleb Dressel 50m analysis','swimming stretching routine','dryland training swimming','swimming race psychology','swimmer nutrition meal plan'].map(q=>`<div style="padding:6px 0;font-size:13px;border-bottom:1px solid #2a3342"><em>${q}</em></div>`).join('')}
|
||
</div>
|
||
<div class="card">
|
||
<h3>⭐ Рейтинг</h3>
|
||
<p class="muted">Топ спортсменов (по голосам):</p>
|
||
<div id="rankList"></div>
|
||
<button class="btn small outline" onclick="voteRandom()" style="margin-top:8px">⭐ Проголосовать</button>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// === VITAMINS ===
|
||
function renderVitamins(){
|
||
const el=document.getElementById('vitCheck');
|
||
if(!el)return;
|
||
const today=new Date().toISOString().slice(0,10);
|
||
const taken=getMy('vtaken')||{};
|
||
const vits=[{id:'d3',n:'Витамин D3'},{id:'omega',n:'Омега-3'},{id:'mg',n:'Магний'},{id:'zn',n:'Цинк'},{id:'bcaa',n:'BCAA'}];
|
||
el.innerHTML=vits.map(v=>{
|
||
const ok=taken[today]&&taken[today][v.id];
|
||
return `<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;font-size:14px"><input type="checkbox" ${ok?'checked':''} onchange="toggleVit('${v.id}')">${v.n}</label>`;
|
||
}).join('');
|
||
}
|
||
function toggleVit(id){
|
||
const today=new Date().toISOString().slice(0,10);
|
||
let taken=getMy('vtaken')||{};
|
||
if(!taken[today])taken[today]={};
|
||
taken[today][id]=!taken[today][id];
|
||
setMy('vtaken',taken);
|
||
renderVitamins();
|
||
}
|
||
function addSleepEntry(){
|
||
const h=prompt('Часов сна:');const p=prompt('Пульс утром:');
|
||
if(!h&&!p)return;
|
||
const s=getMyArr('sleep');s.unshift({date:new Date().toISOString().slice(0,10),hours:+h||0,pulse:+p||0});
|
||
setMyArr('sleep',s);toast('Сохранено!');
|
||
}
|
||
|
||
// === RANKING ===
|
||
function renderRanking(){
|
||
const el=document.getElementById('rankList');if(!el)return;
|
||
const users=LS('users')||[];
|
||
const sorted=[...users].filter(u=>u.stars>0).sort((a,b)=>b.stars-a.stars).slice(0,5);
|
||
if(!sorted.length){el.innerHTML='<p class="muted">Пока никто не проголосовал</p>';return}
|
||
el.innerHTML=sorted.map((u,i)=>`<div style="display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid #2a3342"><span style="color:${i===0?'#FFD700':i===1?'#C0C0C0':i===2?'#CD7F32':'var(--gray-500)'}">${i+1}.</span><span>${u.name}</span><span style="margin-left:auto;color:var(--cyan);font-size:13px">⭐ ${u.stars}</span></div>`).join('');
|
||
}
|
||
function voteRandom(){
|
||
const users=LS('users')||[];
|
||
const others=users.filter(u=>u.id!==uid());
|
||
if(!others.length){toast('Нет других пользователей!');return}
|
||
const target=others[Math.floor(Math.random()*others.length)];
|
||
const voterKey='voted_'+target.id;
|
||
if(localStorage.getItem('g_'+voterKey)){toast('Ты уже голосовал за '+target.name+'!');return}
|
||
target.stars=(target.stars||0)+1;
|
||
const idx=users.findIndex(u=>u.id===target.id);
|
||
if(idx>=0){users[idx]=target;SS('users',users)}
|
||
localStorage.setItem('g_'+voterKey,'1');
|
||
renderRanking();
|
||
toast('Голос за '+target.name+' учтён!');
|
||
}
|
||
|
||
// === LOGOUT ===
|
||
function doLogout(){
|
||
currentUser=null;currentChat=null;
|
||
document.getElementById('appScreen').classList.remove('active');
|
||
document.getElementById('loginScreen').classList.add('active');
|
||
document.getElementById('lUser').value='';document.getElementById('lPass').value='';
|
||
}
|
||
|
||
// === CITIES ===
|
||
const cityData={'Казахстан':['Астана','Алматы','Шымкент','Актобе','Караганда','Тараз','Павлодар','Семей','Атырау','Костанай'],'Россия':['Москва','Санкт-Петербург','Новосибирск','Екатеринбург','Казань','Омск','Уфа'],'США':['Нью-Йорк','Лос-Анджелес','Чикаго','Хьюстон','Майами','Бостон'],'Турция':['Стамбул','Анкара','Измир','Анталья','Бурса'],'Узбекистан':['Ташкент','Самарканд','Бухара'],'Кыргызстан':['Бишкек','Ош'],'Украина':['Киев','Харьков','Одесса','Львов'],'Беларусь':['Минск','Гомель','Брест'],'Германия':['Берлин','Мюнхен','Гамбург'],'Франция':['Париж','Марсель','Лион'],'Великобритания':['Лондон','Манчестер'],'Италия':['Рим','Милан'],'Испания':['Мадрид','Барселона'],'Япония':['Токио','Осака'],'Китай':['Пекин','Шанхай'],
|
||
'ОАЭ':['Дубай','Абу-Даби'],'Азербайджан':['Баку','Гянджа'],'ЮАР':['Йоханнесбург','Кейптаун'],'Канада':['Торонто','Ванкувер','Монреаль'],'Австралия':['Сидней','Мельбурн'],'Бразилия':['Сан-Паулу','Рио']};
|
||
function updateCities(){
|
||
const c=document.getElementById('rCountry').value;
|
||
const dl=document.getElementById('cityList');dl.innerHTML='';
|
||
(cityData[c]||[]).forEach(city=>{const o=document.createElement('option');o.value=city;dl.appendChild(o)});
|
||
}
|
||
|
||
// === RENDER ALL ===
|
||
function renderAll(){
|
||
renderPage();
|
||
setTimeout(()=>{renderVitamins();renderRanking()},200);
|
||
}
|
||
|
||
// === INIT ===
|
||
if('serviceWorker'in navigator)navigator.serviceWorker.register('sw.js').catch(()=>{});
|
||
document.getElementById('loginScreen').classList.add('active');
|
||
</script>
|
||
</body>
|
||
</html>
|