galikon/index.html

882 lines
51 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,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}
*{box-sizing:border-box;margin:0;padding:0}
body{font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100);-webkit-tap-highlight-color:transparent}
header{background:var(--ink);color:var(--white);padding:12px 20px;position:sticky;top:0;z-index:100;display:flex;align-items:center;justify-content:space-between;gap:12px}
header h1{font-size:20px;font-weight:800;white-space:nowrap}
header h1 span{color:var(--cyan)}
.header-right{display:flex;align-items:center;gap:8px}
.header-right .avatar{width:34px;height:34px;border-radius:50%;background:var(--cyan);color:var(--ink);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:16px;cursor:pointer;overflow:hidden}
.header-right .avatar img{width:100%;height:100%;object-fit:cover}
.header-right .profile-name{font-size:13px;color:var(--cyan);max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
nav.tabs{display:flex;overflow-x:auto;gap:4px;padding:8px 20px;background:var(--ink);position:sticky;top:57px;z-index:99;-webkit-overflow-scrolling:touch}
nav.tabs::-webkit-scrollbar{display:none}
.tab-btn{flex-shrink:0;padding:8px 14px;border-radius:20px;border:none;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;background:#1a2332;color:#9aa3b2;transition:all .2s}
.tab-btn.active{background:var(--cyan);color:var(--ink)}
.tab-btn:active{transform:scale(.95)}
main{padding:20px;max-width:900px;margin:0 auto}
.tab-content{display:none}
.tab-content.active{display:block}
.card{background:var(--white);border-radius:16px;padding:24px;margin-bottom:16px;box-shadow:0 1px 4px rgba(0,0,0,.06)}
.card h3{font-size:18px;font-weight:700;margin-bottom:12px}
.card h3 .emoji{margin-right:8px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.stat-box{text-align:center;padding:16px 8px;background:var(--gray-100);border-radius:12px}
.stat-box .num{font-size:28px;font-weight:800;color:var(--cyan);line-height:1}
.stat-box .lbl{font-size:11px;color:var(--gray-500);margin-top:4px}
input,select,textarea{width:100%;padding:12px;border:2px solid var(--gray-100);border-radius:10px;font:inherit;font-size:15px;margin-bottom:10px;background:var(--white);transition:border-color .2s}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--cyan)}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:10px 22px;border-radius:10px;font-weight:700;border:none;font-size:15px;cursor:pointer;transition:transform .2s;text-align:center}
.btn:active{transform:scale(.96)}
.btn.danger{background:var(--red);color:#fff}
.btn.small{padding:6px 14px;font-size:13px}
.btn.outline{background:transparent;border:2px solid var(--cyan);color:var(--cyan)}
.check-row{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--gray-100)}
.check-row:last-child{border-bottom:none}
.check-row label{flex:1;font-size:15px;cursor:pointer}
.check-row input[type=checkbox]{width:20px;height:20px;margin:0;accent-color:var(--cyan)}
table{width:100%;border-collapse:collapse;font-size:14px}
th,td{padding:8px 10px;text-align:left;border-bottom:1px solid var(--gray-100)}
th{color:var(--gray-500);font-weight:600;font-size:12px;text-transform:uppercase}
td .badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:12px;font-weight:600}
.badge.gold{background:#FFF3CD;color:#856404}
.badge.blue{background:var(--cyan-50);color:var(--ink)}
.badge.green{background:#D4EDDA;color:#155724}
.video-preview{width:100%;border-radius:12px;max-height:300px;object-fit:contain;background:#000}
.photo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px}
.photo-grid img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:12px;cursor:pointer}
.photo-grid img:active{opacity:.8}
label.file-btn{display:inline-block;background:var(--gray-100);padding:12px 20px;border-radius:10px;font-weight:600;cursor:pointer;font-size:14px;margin-bottom:10px}
label.file-btn:active{background:var(--cyan-50)}
input[type=file]{display:none}
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--ink);color:var(--cyan);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}
.progress-bar{height:8px;background:var(--gray-100);border-radius:4px;overflow:hidden;margin-top:8px}
.progress-bar .fill{height:100%;background:var(--cyan);border-radius:4px;transition:width .5s}
.empty{text-align:center;padding:40px 20px;color:var(--gray-500)}
.empty .big{font-size:48px;margin-bottom:12px}
.lightbox{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.92);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}
/* PROFILE SCREEN */
.profile-screen{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--ink);z-index:300;display:flex;flex-direction:column;overflow-y:auto;padding:24px}
.profile-screen h2{color:var(--white);text-align:center;margin-bottom:8px;font-size:24px}
.profile-screen h2 span{color:var(--cyan)}
.profile-screen .sub{color:var(--gray-500);text-align:center;margin-bottom:24px;font-size:15px}
.profile-screen .card{background:#1a2332;border:1px solid #2a3342;margin-bottom:12px}
.profile-screen .card h3{color:var(--white)}
.profile-screen input,.profile-screen select,.profile-screen textarea{background:#0F1218;border-color:#2a3342;color:var(--white)}
.profile-screen input::placeholder{color:#5b6573}
.profile-screen label{color:var(--gray-500);font-size:13px;margin-bottom:4px;display:block}
.profile-list-item{display:flex;align-items:center;gap:14px;padding:14px;background:#1a2332;border-radius:12px;margin-bottom:8px;cursor:pointer;border:2px solid transparent;transition:border-color .2s}
.profile-list-item:hover,.profile-list-item.selected{border-color:var(--cyan)}
.profile-list-item .av{width:44px;height:44px;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;overflow:hidden}
.profile-list-item .av img{width:100%;height:100%;object-fit:cover}
.profile-list-item .info{flex:1;color:var(--white)}
.profile-list-item .info .name{font-weight:700;font-size:15px}
.profile-list-item .info .sport{font-size:12px;color:var(--gray-500)}
.app-screen{display:none}
.app-screen.active{display:block}
.profile-screen.hidden{display:none}
@media(max-width:480px){
.grid2{grid-template-columns:1fr}
header h1{font-size:16px}
nav.tabs{gap:2px;padding:6px 10px}
.tab-btn{padding:7px 10px;font-size:12px}
main{padding:12px}
}
</style>
</head>
<body>
<!-- PROFILE SCREEN -->
<div class="profile-screen" id="profileScreen">
<h2>&#x1F3CA; <span>Галикон</span></h2>
<p class="sub">Приложение для спортсменов. Создай свой профиль.</p>
<div class="card" id="regForm">
<h3>&#x270F; Регистрация</h3>
<input type="text" id="regName" placeholder="Фамилия Имя Отчество">
<div class="grid2">
<select id="regSport">
<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>>
<input type="number" id="regAge" placeholder="Возраст (авто)" min="5" max="100" readonly style="background:var(--gray-100)">
<input type="date" id="regBirth" placeholder="Дата рождения" onchange="calcAge()">
</div>
<input type="text" id="regClub" placeholder="Клуб / спортивная школа">
<div class="grid2">
<select id="regCountry" onchange="updateCityList()">
<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><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="regCity" placeholder="Город" list="cityList" autocomplete="off">
<datalist id="cityList"></datalist>
</div>
<input type="text" id="regCoach" placeholder="Тренер (ФИО)">
<div class="grid2">
<input type="text" id="regRank" placeholder="Разряд / звание">
<input type="text" id="regGoal" placeholder="Главная цель">
</div>
<label class="file-btn" style="margin-bottom:10px">&#x1F4F7; Фото профиля<input type="file" accept="image/*" id="regPhoto" onchange="previewRegPhoto()"></label>
<img id="regPhotoPreview" style="width:80px;height:80px;border-radius:50%;object-fit:cover;display:none;margin:0 auto 10px">
<button class="btn" onclick="registerProfile()" style="width:100%">&#x1F44D; Создать профиль</button>
</div>
<div style="text-align:center;color:var(--gray-500);font-size:14px;margin-bottom:12px">— или —</div>
<div id="profileList"></div>
</div>
<!-- APP SCREEN -->
<div class="app-screen" id="appScreen">
<header>
<h1>&#x1F3CA; <span>Галикон</span></h1>
<div class="header-right">
<span class="profile-name" id="headerName"></span>
<div class="avatar" id="headerAvatar" onclick="showProfileScreen()"></div>
</div>
</header>
<nav class="tabs">
<button class="tab-btn active" data-tab="diary">&#x1F4D6; Дневник</button>
<button class="tab-btn" data-tab="health">&#x2764; Здоровье</button>
<button class="tab-btn" data-tab="norms">&#x1F4CA; Нормативы</button>
<button class="tab-btn" data-tab="video">&#x1F3AC; Видео</button>
<button class="tab-btn" data-tab="photos">&#x1F4F7; Фото</button>
<button class="tab-btn" data-tab="grades">&#x1F393; Оценки</button>
<button class="tab-btn" data-tab="lessons">&#x1F4DA; Уроки</button>
<button class="tab-btn" data-tab="ranking">&#x2B50; Рейтинг</button>
</nav>
<main>
<!-- ДНЕВНИК -->
<div class="tab-content active" id="diary">
<div class="card">
<h3><span class="emoji">&#x270F;</span>Новая запись</h3>
<input type="date" id="diary-date">
<select id="diary-type"><option value="">Тип тренировки</option><option>Скорость</option><option>Техника</option><option>Выносливость</option><option>ОФП</option><option>Соревнование</option></select>
<input type="number" id="diary-km" placeholder="Километраж (км)" step="0.1" min="0">
<input type="text" id="diary-time" placeholder="Лучшее время (например: 33.8)">
<input type="number" id="diary-feel" placeholder="Самочувствие (1-5)" min="1" max="5">
<input type="text" id="diary-note" placeholder="Заметка (что получилось, над чем работать)">
<button class="btn" onclick="addDiary()" style="width:100%">&#x2705; Сохранить</button>
</div>
<div id="diary-list" class="empty"><div class="big">&#x1F4AD;</div>Пока нет записей. Добавь первую!</div>
</div>
<!-- ЗДОРОВЬЕ -->
<div class="tab-content" id="health">
<div class="card"><h3><span class="emoji">&#x1F48A;</span>Витамины на сегодня</h3><div id="vitamins-check"></div></div>
<div class="card">
<h3><span class="emoji">&#x1F4A4;</span>Сон и самочувствие</h3>
<div class="grid2">
<input type="number" id="sleep-hours" placeholder="Часов сна" min="0" max="24" step="0.5">
<input type="number" id="morning-pulse" placeholder="Пульс утром" min="30" max="150">
</div>
<button class="btn" onclick="saveSleep()" style="width:100%">Сохранить</button>
<div style="margin-top:12px;font-size:13px;color:var(--gray-500)" id="sleep-stats"></div>
</div>
<div class="card">
<h3><span class="emoji">&#x1F4C5;</span>Анализы</h3>
<select id="test-select"><option value="">Выбери анализ</option><option value="hb">Гемоглобин (г/л)</option><option value="ferritin">Ферритин (мкг/л)</option><option value="vitd">Витамин D (нг/мл)</option><option value="b12">Витамин B12 (пг/мл)</option></select>
<input type="number" id="test-value" placeholder="Значение"><input type="date" id="test-date">
<button class="btn" onclick="saveTest()" style="width:100%">Сохранить анализ</button>
<div id="test-history" style="margin-top:8px;font-size:13px;color:var(--gray-500)"></div>
</div>
</div>
<!-- НОРМАТИВЫ -->
<div class="tab-content" id="norms">
<div class="card">
<h3><span class="emoji">&#x1F3CA;</span>Разряды — 50 м в/с (мужчины, бассейн 50 м)</h3>
<table>
<thead><tr><th>Разряд</th><th>50 м в/с</th><th>100 м в/с</th><th>400 м в/с</th></tr></thead>
<tbody>
<tr><td><span class="badge gold">МСМК</span></td><td>21.50″</td><td>47.00″</td><td>3:48.00</td></tr>
<tr><td><span class="badge blue">МС</span></td><td>23.00″</td><td>50.50″</td><td>4:05.00</td></tr>
<tr><td><span class="badge green">КМС</span></td><td>25.00″</td><td>54.50″</td><td>4:25.00</td></tr>
<tr><td>1 взр.</td><td>28.00″</td><td>1:01.00</td><td>5:00.00</td></tr>
<tr><td>2 взр.</td><td>31.50″</td><td>1:09.00</td><td>5:40.00</td></tr>
<tr><td>3 взр.</td><td>35.00″</td><td>1:17.50</td><td>6:20.00</td></tr>
<tr><td>1 юн.</td><td>40.00″</td><td>1:29.00</td><td>7:10.00</td></tr>
<tr><td>2 юн.</td><td>47.00″</td><td>1:45.00</td><td>8:10.00</td></tr>
</tbody>
</table>
</div>
</div>
<!-- ВИДЕО -->
<div class="tab-content" id="video">
<div class="card">
<h3><span class="emoji">&#x1F4F9;</span>Загрузи видео заплыва</h3>
<p style="color:var(--gray-500);font-size:14px;margin-bottom:12px">Сними заплыв сбоку от бортика. Загрузи — смотри в повторе, анализируй технику.</p>
<label class="file-btn">&#x1F4C1; Выбрать видео<input type="file" accept="video/*" onchange="uploadVideo(this)"></label>
<div id="video-container"></div>
</div>
<div id="video-list" class="empty"><div class="big">&#x1F3A5;</div>Загрузи первое видео заплыва!</div>
</div>
<!-- ФОТО -->
<div class="tab-content" id="photos">
<div class="card">
<h3><span class="emoji">&#x1F4F8;</span>Добавить фото</h3>
<label class="file-btn">&#x1F4C1; Выбрать фото<input type="file" accept="image/*" multiple onchange="uploadPhotos(this)"></label>
</div>
<div class="card"><h3><span class="emoji">&#x1F5BC;</span>Галерея</h3><div class="photo-grid" id="photo-grid"><div class="empty" style="grid-column:1/-1"><div class="big">&#x1F4F7;</div>Добавь первые фото!</div></div></div>
</div>
<!-- ОЦЕНКИ -->
<div class="tab-content" id="grades">
<div class="card">
<h3><span class="emoji">&#x1F4DD;</span>Добавить оценку</h3>
<div class="grid2">
<input type="text" id="grade-subject" placeholder="Предмет">
<select id="grade-value"><option value="5">5</option><option value="4">4</option><option value="3">3</option><option value="2">2</option></select>
</div>
<button class="btn" onclick="addGrade()" style="width:100%">Добавить</button>
</div>
<div class="card">
<h3><span class="emoji">&#x1F4CA;</span>Успеваемость</h3>
<div class="grid2" style="margin-bottom:12px">
<div class="stat-box"><div class="num" id="gpa-display"></div><div class="lbl">Средний балл</div></div>
<div class="stat-box"><div class="num" id="grades-count">0</div><div class="lbl">Оценок</div></div>
</div>
<div id="grades-list" class="empty"><div class="big">&#x1F393;</div>Добавь оценки</div>
</div>
</div>
<!-- РЕЙТИНГ -->
<div class="tab-content" id="ranking">
<div class="card">
<h3><span class="emoji">&#x1F3C6;</span>Рейтинг спортсменов</h3>
<p style="color:var(--gray-500);font-size:13px;margin-bottom:12px">&#x2B50; = голоса родителей. Нажми, чтобы поддержать!</p>
<div id="swimmer-ranking"><div class="empty"><div class="big">&#x1F3CA;</div>Добавьте спортсменов</div></div>
<button class="btn outline" onclick="addSwimmerForm()" style="width:100%;margin-top:8px">+ Добавить спортсмена</button>
<div id="swimmer-form" style="display:none;margin-top:12px">
<input id="sw-name" placeholder="ФИО спортсмена">
<div class="grid2">
<input id="sw-club" placeholder="Клуб">
<input id="sw-time" placeholder="Лучшее время (50 м в/с)">
</div>
<button class="btn" onclick="addSwimmer()" style="width:100%">Добавить</button>
</div>
</div>
<div class="card">
<h3><span class="emoji">&#x1F468;&#x200D;&#x1F3EB;</span>Рейтинг тренеров</h3>
<p style="color:var(--gray-500);font-size:13px;margin-bottom:12px">&#x2B50; = голоса родителей. Оцени тренера!</p>
<div id="coach-ranking"><div class="empty"><div class="big">&#x1F3CB;</div>Добавьте тренеров</div></div>
<button class="btn outline" onclick="addCoachForm()" style="width:100%;margin-top:8px">+ Добавить тренера</button>
<div id="coach-form" style="display:none;margin-top:12px">
<input id="co-name" placeholder="ФИО тренера">
<div class="grid2">
<input id="co-club" placeholder="Клуб">
<input id="co-exp" placeholder="Стаж (лет)">
</div>
<textarea id="co-achieve" placeholder="Достижения (воспитанники, разряды...)" rows="2"></textarea>
<button class="btn" onclick="addCoach()" style="width:100%">Добавить</button>
</div>
</div>
</div>
<!-- ВИДЕОУРОКИ -->
<div class="tab-content" id="lessons">
<div class="card">
<h3><span class="emoji">&#x1F4FA;</span>Обучающие видео</h3>
<p style="color:var(--gray-500);font-size:14px;margin-bottom:12px">Найди на YouTube эти запросы — лучшие уроки по технике:</p>
<div id="lesson-list"></div>
</div>
</div>
</main>
</div>
<div class="toast" id="toast">Сохранено!</div>
<div class="lightbox" id="lightbox" style="display:none" onclick="this.style.display='none'"></div>
<script>
// === STORAGE HELPERS ===
const LS = key => { try { const v = localStorage.getItem('galikon_'+key); return v ? JSON.parse(v) : null } catch { return null } };
const SS = (key, val) => { try { localStorage.setItem('galikon_'+key, JSON.stringify(val)) } catch { toast('Память браузера заполнена!') } };
let currentProfileId = null;
function pid() { return currentProfileId || 'default'; }
function toast(msg) {
const t = document.getElementById('toast');
t.textContent = msg; t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2000);
}
// === PROFILE SYSTEM ===
function registerProfile() {
const name = document.getElementById('regName').value.trim();
if (!name) return toast('Введи ФИО!');
const profile = {
id: Date.now(),
name,
sport: document.getElementById('regSport').value.trim(),
age: document.getElementById('regAge').value,
birth: document.getElementById('regBirth').value,
club: document.getElementById('regClub').value.trim(),
country: document.getElementById('regCountry').value,
city: document.getElementById('regCity').value.trim(),
coach: document.getElementById('regCoach').value.trim(),
rank: document.getElementById('regRank').value.trim(),
goal: document.getElementById('regGoal').value.trim(),
photo: document.getElementById('regPhotoPreview').src || '',
created: new Date().toISOString()
};
if (profile.photo === window.location.href) profile.photo = '';
const profiles = LS('profiles') || [];
profiles.push(profile);
SS('profiles', profiles);
// Clear form
['regName','regSport','regAge','regBirth','regCountry','regClub','regCity','regCoach','regRank','regGoal'].forEach(id => document.getElementById(id).value = '');
document.getElementById('regPhotoPreview').style.display = 'none';
loginProfile(profile.id);
}
const cityData = {
'Казахстан': ['Астана','Алматы','Шымкент','Актобе','Караганда','Тараз','Павлодар','Усть-Каменогорск','Семей','Атырау','Костанай','Кызылорда','Уральск','Петропавловск','Актау','Туркестан','Кокшетау','Талдыкорган'],
'Россия': ['Москва','Санкт-Петербург','Новосибирск','Екатеринбург','Казань','Нижний Новгород','Челябинск','Самара','Омск','Ростов-на-Дону','Уфа','Красноярск','Воронеж','Пермь','Волгоград'],
'США': ['Нью-Йорк','Лос-Анджелес','Чикаго','Хьюстон','Финикс','Филадельфия','Сан-Антонио','Сан-Диего','Даллас','Сан-Хосе','Остин','Майами','Бостон','Сиэтл','Денвер'],
'Китай': ['Пекин','Шанхай','Гуанчжоу','Шэньчжэнь','Чэнду','Ухань','Нанкин','Ханчжоу','Тяньцзинь','Чунцин'],
'Турция': ['Стамбул','Анкара','Измир','Бурса','Анталья','Адана','Конья','Газиантеп','Мерсин','Диярбакыр'],
'Украина': ['Киев','Харьков','Одесса','Днепр','Львов','Запорожье','Винница','Полтава','Чернигов','Ивано-Франковск'],
'Узбекистан': ['Ташкент','Самарканд','Бухара','Наманган','Андижан','Фергана','Нукус','Карши','Коканд','Маргилан'],
'Германия': ['Берлин','Мюнхен','Гамбург','Кёльн','Франкфурт','Штутгарт','Дюссельдорф','Лейпциг','Дортмунд','Эссен'],
'Франция': ['Париж','Марсель','Лион','Тулуза','Ницца','Нант','Страсбург','Монпелье','Бордо','Лилль'],
'Великобритания': ['Лондон','Манчестер','Бирмингем','Лидс','Глазго','Ливерпуль','Бристоль','Эдинбург','Шеффилд','Ноттингем'],
'Япония': ['Токио','Осака','Киото','Иокогама','Нагоя','Саппоро','Фукуока','Кобе','Хиросима','Сендай'],
'Корея Южная': ['Сеул','Пусан','Инчхон','Тэгу','Тэджон','Кванджу','Сувон','Ульсан','Чханвон','Коян'],
'ОАЭ': ['Дубай','Абу-Даби','Шарджа','Аль-Айн','Аджман','Рас-эль-Хайма','Фуджейра','Умм-эль-Кувейн'],
'Кыргызстан': ['Бишкек','Ош','Джалал-Абад','Каракол','Токмок','Нарын','Балыкчы','Талас'],
'Азербайджан': ['Баку','Гянджа','Сумгаит','Мингечаур','Ширван','Нахичевань','Шеки','Ленкорань'],
'Беларусь': ['Минск','Гомель','Могилёв','Витебск','Гродно','Брест','Бобруйск','Барановичи'],
'Италия': ['Рим','Милан','Неаполь','Турин','Флоренция','Болонья','Венеция','Генуя','Верона','Палермо'],
'Испания': ['Мадрид','Барселона','Валенсия','Севилья','Малага','Бильбао','Сарагоса','Аликанте','Гранада','Пальма'],
'Канада': ['Торонто','Монреаль','Ванкувер','Калгари','Эдмонтон','Оттава','Квебек','Виннипег','Гамильтон','Китченер'],
'Австралия': ['Сидней','Мельбурн','Брисбен','Перт','Аделаида','Голд-Кост','Канберра','Ньюкасл','Хобарт','Дарвин'],
'Бразилия': ['Сан-Паулу','Рио-де-Жанейро','Бразилиа','Салвадор','Форталеза','Белу-Оризонти','Манаус','Куритиба','Ресифи','Порту-Алегри'],
'Индия': ['Дели','Мумбаи','Бангалор','Ченнаи','Калькутта','Хайдарабад','Пуна','Ахмадабад','Джайпур','Лакхнау']
};
function updateCityList() {
const country = document.getElementById('regCountry').value;
const datalist = document.getElementById('cityList');
datalist.innerHTML = '';
const cities = cityData[country] || [];
cities.forEach(c => { const opt = document.createElement('option'); opt.value = c; datalist.appendChild(opt); });
}
function calcAge() {
const b = document.getElementById('regBirth').value;
if (!b) return;
const birth = new Date(b);
const today = new Date();
let age = today.getFullYear() - birth.getFullYear();
const m = today.getMonth() - birth.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
document.getElementById('regAge').value = age;
}
function previewRegPhoto() {
const f = document.getElementById('regPhoto').files[0];
if (!f) return;
const r = new FileReader();
r.onload = e => { const img = document.getElementById('regPhotoPreview'); img.src = e.target.result; img.style.display = 'block'; };
r.readAsDataURL(f);
}
function loginProfile(id) {
currentProfileId = id;
const profiles = LS('profiles') || [];
const p = profiles.find(x => x.id === id);
if (!p) return;
document.getElementById('profileScreen').classList.add('hidden');
document.getElementById('appScreen').classList.add('active');
document.getElementById('headerName').textContent = p.name.split(' ')[0];
const av = document.getElementById('headerAvatar');
if (p.photo) {
av.innerHTML = `<img src="${p.photo}" alt="">`;
} else {
av.innerHTML = p.name.charAt(0).toUpperCase();
}
renderAll();
renderRankings();
}
function showProfileScreen() {
document.getElementById('profileScreen').classList.remove('hidden');
document.getElementById('appScreen').classList.remove('active');
renderProfileList();
}
function deleteProfile(id) {
if (!confirm('Удалить профиль? Все данные будут потеряны.')) return;
const profiles = (LS('profiles') || []).filter(p => p.id !== id);
SS('profiles', profiles);
if (currentProfileId === id) { currentProfileId = null; document.getElementById('appScreen').classList.remove('active'); }
renderProfileList();
}
function renderProfileList() {
const profiles = LS('profiles') || [];
const el = document.getElementById('profileList');
if (!profiles.length) { el.innerHTML = '<p style="color:var(--gray-500);text-align:center">Нет профилей. Создай первый!</p>'; return; }
el.innerHTML = profiles.map(p => `
<div class="profile-list-item ${p.id === currentProfileId ? 'selected' : ''}" onclick="loginProfile(${p.id})">
<div class="av">${p.photo ? `<img src="${p.photo}">` : p.name.charAt(0).toUpperCase()}</div>
<div class="info">
<div class="name">${p.name}</div>
<div class="sport">${[p.sport, p.club, p.age ? p.age+' лет' : '', p.birth ? '&#x1F382; '+p.birth : '', p.country ? '&#x1F30D; '+p.country+(p.city?'/'+p.city:'') : p.city, p.rank].filter(Boolean).join(' · ')}</div>
</div>
<button class="btn danger small" style="flex-shrink:0" onclick="event.stopPropagation();deleteProfile(${p.id})">&#x1F5D1;</button>
</div>
`).join('');
}
// === TABS ===
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
btn.classList.add('active');
document.getElementById(btn.dataset.tab).classList.add('active');
});
});
// === DIARY ===
function addDiary() {
const entry = {
date: document.getElementById('diary-date').value || new Date().toISOString().slice(0,10),
type: document.getElementById('diary-type').value || 'Тренировка',
km: document.getElementById('diary-km').value || '0',
time: document.getElementById('diary-time').value || '—',
feel: document.getElementById('diary-feel').value || '—',
note: document.getElementById('diary-note').value || '—',
id: Date.now()
};
if (!entry.date) return toast('Выбери дату!');
const all = LS('diary_all') || {};
if (!all[pid()]) all[pid()] = [];
all[pid()].unshift(entry);
SS('diary_all', all);
['diary-date','diary-type','diary-km','diary-time','diary-feel','diary-note'].forEach(id => document.getElementById(id).value = '');
renderDiary();
toast('Запись добавлена!');
}
function renderDiary() {
const all = LS('diary_all') || {};
const diary = all[pid()] || [];
const el = document.getElementById('diary-list');
if (!diary.length) { el.innerHTML = '<div class="empty"><div class="big">&#x1F4AD;</div>Пока нет записей. Добавь первую!</div>'; return; }
el.innerHTML = diary.slice(0,30).map(e => `<div class="card" style="margin-bottom:8px;padding:16px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
<strong>${e.date}</strong> <span class="badge blue">${e.type}</span>
</div>
<div style="font-size:14px;color:var(--gray-500)">
&#x1F4CF; ${e.km} км | &#x23F1; ${e.time} | &#x1F31F; ${e.feel}/5
${e.note !== '—' ? '<br>'+e.note : ''}
</div>
<button class="btn danger small" style="margin-top:6px" onclick="deleteDiary(${e.id})">Удалить</button>
</div>`).join('');
}
function deleteDiary(id) {
const all = LS('diary_all') || {};
if (!all[pid()]) return;
all[pid()] = all[pid()].filter(e => e.id !== id);
SS('diary_all', all);
renderDiary();
toast('Удалено');
}
// === HEALTH ===
const vitamins = [
{ id: 'd3', name: 'Витамин D3', dose: '2000-4000 МЕ', when: 'Утром' },
{ id: 'omega', name: 'Омега-3', dose: '1000 мг', when: 'Утром' },
{ id: 'mg', name: 'Магний', dose: '300 мг', when: 'Перед сном' },
{ id: 'zn', name: 'Цинк', dose: '15 мг', when: 'Утром' },
{ id: 'bcaa', name: 'BCAA', dose: '5 г', when: 'После тренировки' }
];
function renderVitamins() {
const today = new Date().toISOString().slice(0,10);
const all = LS('vitamins_all') || {};
const taken = all[pid()] || {};
const el = document.getElementById('vitamins-check');
el.innerHTML = vitamins.map(v => {
const isTaken = taken[today] && taken[today][v.id];
return `<div class="check-row">
<input type="checkbox" ${isTaken ? 'checked' : ''} onchange="toggleVitamin('${v.id}')" id="vit_${v.id}">
<label for="vit_${v.id}">${v.name} (${v.dose}) — ${v.when}</label>
</div>`;
}).join('') + `<div style="margin-top:8px;font-size:13px;color:var(--gray-500)">Принято: ${countTaken()} из ${vitamins.length}</div>`;
}
function countTaken() {
const all = LS('vitamins_all') || {};
const taken = all[pid()] || {};
const today = new Date().toISOString().slice(0,10);
return taken[today] ? Object.values(taken[today]).filter(Boolean).length : 0;
}
function toggleVitamin(id) {
const today = new Date().toISOString().slice(0,10);
const all = LS('vitamins_all') || {};
if (!all[pid()]) all[pid()] = {};
if (!all[pid()][today]) all[pid()][today] = {};
all[pid()][today][id] = !all[pid()][today][id];
SS('vitamins_all', all);
renderVitamins();
}
function saveSleep() {
const h = document.getElementById('sleep-hours').value;
const p = document.getElementById('morning-pulse').value;
if (!h && !p) return toast('Введи данные!');
const all = LS('sleep_all') || {};
if (!all[pid()]) all[pid()] = [];
all[pid()].unshift({ date: new Date().toISOString().slice(0,10), hours: +h || 0, pulse: +p || 0 });
SS('sleep_all', all);
document.getElementById('sleep-hours').value = '';
document.getElementById('morning-pulse').value = '';
renderSleep();
toast('Сохранено!');
}
function renderSleep() {
const all = LS('sleep_all') || {};
const sleep = all[pid()] || [];
const el = document.getElementById('sleep-stats');
if (!sleep.length) { el.innerHTML = ''; return; }
const last7 = sleep.slice(0,7).filter(e => e.hours > 0);
if (!last7.length) { el.innerHTML = ''; return; }
const avgH = (last7.reduce((s,e) => s+e.hours, 0) / last7.length).toFixed(1);
const avgP = last7.filter(e => e.pulse > 0).length ? (last7.filter(e=>e.pulse>0).reduce((s,e) => s+e.pulse, 0) / last7.filter(e=>e.pulse>0).length).toFixed(0) : '—';
el.innerHTML = `Последние 7 дней: сон <strong>${avgH} ч</strong>, пульс <strong>${avgP} уд/мин</strong>`;
}
function saveTest() {
const type = document.getElementById('test-select').value;
const val = document.getElementById('test-value').value;
const date = document.getElementById('test-date').value;
if (!type || !val || !date) return toast('Заполни все поля!');
const all = LS('tests_all') || {};
if (!all[pid()]) all[pid()] = {};
if (!all[pid()][type]) all[pid()][type] = [];
all[pid()][type].push({ date, value: +val });
SS('tests_all', all);
document.getElementById('test-value').value = '';
document.getElementById('test-date').value = '';
renderTests();
toast('Анализ сохранён!');
}
function renderTests() {
const all = LS('tests_all') || {};
const tests = all[pid()] || {};
const labels = { hb: 'Гемоглобин (120-150)', ferritin: 'Ферритин (30-100)', vitd: 'Витамин D (50-80)', b12: 'B12 (200-900)' };
const el = document.getElementById('test-history');
el.innerHTML = Object.entries(tests).map(([k,vals]) => {
const last = vals.slice(-3).reverse().map(v => `${v.date}: <strong>${v.value}</strong>`).join(' | ');
return `<div style="margin-bottom:6px;font-size:13px">${labels[k] || k}: ${last}</div>`;
}).join('') || 'Нет сохранённых анализов';
}
// === VIDEO ===
function uploadVideo(input) {
const file = input.files[0];
if (!file) return;
if (file.size > 100*1024*1024) return toast('Видео слишком большое (макс 100 МБ)');
const reader = new FileReader();
reader.onload = function(e) {
const videoData = { name: file.name, data: e.target.result, date: new Date().toLocaleDateString('ru-RU'), id: Date.now() };
const all = LS('videos_all') || {};
if (!all[pid()]) all[pid()] = [];
all[pid()].unshift(videoData);
SS('videos_all', all);
document.getElementById('video-container').innerHTML = `<video class="video-preview" controls src="${e.target.result}" style="margin-top:8px"></video>`;
renderVideos();
toast('Видео загружено!');
};
reader.readAsDataURL(file);
}
function renderVideos() {
const all = LS('videos_all') || {};
const videos = all[pid()] || [];
const el = document.getElementById('video-list');
if (!videos.length) { el.innerHTML = '<div class="empty"><div class="big">&#x1F3A5;</div>Загрузи первое видео!</div>'; return; }
el.innerHTML = videos.map(v => `<div class="card" style="padding:12px;margin-bottom:8px;cursor:pointer" onclick="showLightbox('video','${v.id}')"><strong>${v.name}</strong> <span style="color:var(--gray-500);font-size:13px">— ${v.date}</span></div>`).join('');
}
// === PHOTOS ===
function uploadPhotos(input) {
const files = Array.from(input.files);
if (!files.length) return;
const all = LS('photos_all') || {};
if (!all[pid()]) all[pid()] = [];
let loaded = 0;
files.slice(0,10).forEach(file => {
if (file.size > 10*1024*1024) return;
const reader = new FileReader();
reader.onload = function(e) {
all[pid()].unshift({ name: file.name, data: e.target.result, date: new Date().toLocaleDateString('ru-RU'), id: Date.now()+loaded });
loaded++;
if (loaded === Math.min(files.length, 10)) { SS('photos_all', all); renderPhotos(); toast(loaded+' фото!'); }
};
reader.readAsDataURL(file);
});
}
function renderPhotos() {
const all = LS('photos_all') || {};
const photos = all[pid()] || [];
const el = document.getElementById('photo-grid');
if (!photos.length) { el.innerHTML = '<div class="empty" style="grid-column:1/-1"><div class="big">&#x1F4F7;</div>Добавь фото!</div>'; return; }
el.innerHTML = photos.map(p => `<img src="${p.data}" alt="${p.name}" onclick="showLightbox('photo','${p.id}')">`).join('');
}
// === GRADES ===
function addGrade() {
const subject = document.getElementById('grade-subject').value.trim();
const grade = +document.getElementById('grade-value').value;
if (!subject) return toast('Введи предмет!');
const all = LS('grades_all') || {};
if (!all[pid()]) all[pid()] = {};
if (!all[pid()][subject]) all[pid()][subject] = [];
all[pid()][subject].push({ grade, date: new Date().toISOString().slice(0,10) });
SS('grades_all', all);
document.getElementById('grade-subject').value = '';
renderGrades();
toast('Оценка добавлена!');
}
function renderGrades() {
const all = LS('grades_all') || {};
const grades = all[pid()] || {};
const allGrades = [];
Object.entries(grades).forEach(([s,vals]) => vals.forEach(v => allGrades.push({ subject:s, ...v })));
const avg = allGrades.length ? (allGrades.reduce((s,v) => s+v.grade, 0)/allGrades.length).toFixed(2) : '—';
document.getElementById('gpa-display').textContent = avg;
document.getElementById('grades-count').textContent = allGrades.length;
const el = document.getElementById('grades-list');
if (!Object.keys(grades).length) { el.innerHTML = '<div class="empty"><div class="big">&#x1F393;</div>Добавь оценки</div>'; return; }
el.innerHTML = Object.entries(grades).map(([subject, vals]) => {
const subjAvg = (vals.reduce((s,v) => s+v.grade,0)/vals.length).toFixed(1);
return `<div class="card" style="padding:16px;margin-bottom:8px">
<div style="display:flex;justify-content:space-between;align-items:center">
<strong>${subject}</strong>
<span class="badge ${+subjAvg>=4.5?'gold':+subjAvg>=3.5?'blue':''}" style="background:${+subjAvg>=4.5?'#FFF3CD':+subjAvg>=3.5?'var(--cyan-50)':'#FEE'};color:${+subjAvg>=4.5?'#856404':+subjAvg>=3.5?'var(--ink)':'#c00'}">${subjAvg}</span>
</div>
<div style="font-size:13px;color:var(--gray-500)">Последние: ${vals.slice(-5).map(v=>v.grade).join(', ')}</div>
<button class="btn danger small" style="margin-top:6px" onclick="deleteGrades('${subject}')">Сбросить</button>
</div>`;
}).join('');
}
function deleteGrades(subject) {
const all = LS('grades_all') || {};
if (all[pid()]) { delete all[pid()][subject]; SS('grades_all', all); }
renderGrades();
toast('Удалено');
}
// === LIGHTBOX ===
function showLightbox(type, id) {
const lb = document.getElementById('lightbox');
const key = type === 'photo' ? 'photos_all' : 'videos_all';
const all = LS(key) || {};
const items = all[pid()] || [];
const item = items.find(i => i.id == id);
if (!item) return;
lb.innerHTML = type === 'photo' ? `<img src="${item.data}">` : `<video controls src="${item.data}" style="max-width:95%;max-height:90vh"></video>`;
lb.style.display = 'flex';
}
// === RANKING ===
function addSwimmerForm() { document.getElementById('swimmer-form').style.display='block'; }
function addCoachForm() { document.getElementById('coach-form').style.display='block'; }
function addSwimmer() {
const name = document.getElementById('sw-name').value.trim();
if (!name) return toast('Введи ФИО!');
const swimmers = LS('swimmers') || [];
swimmers.push({ id: Date.now(), name, club: document.getElementById('sw-club').value.trim(), time: document.getElementById('sw-time').value.trim(), stars: 0, voters: [] });
SS('swimmers', swimmers);
['sw-name','sw-club','sw-time'].forEach(id => document.getElementById(id).value='');
document.getElementById('swimmer-form').style.display='none';
renderRankings();
toast('Спортсмен добавлен!');
}
function addCoach() {
const name = document.getElementById('co-name').value.trim();
if (!name) return toast('Введи ФИО!');
const coaches = LS('coaches') || [];
coaches.push({ id: Date.now(), name, club: document.getElementById('co-club').value.trim(), exp: document.getElementById('co-exp').value.trim(), achieve: document.getElementById('co-achieve').value.trim(), stars: 0, voters: [] });
SS('coaches', coaches);
['co-name','co-club','co-exp','co-achieve'].forEach(id => document.getElementById(id).value='');
document.getElementById('coach-form').style.display='none';
renderRankings();
toast('Тренер добавлен!');
}
function voteSwimmer(id) {
const swimmers = LS('swimmers') || [];
const s = swimmers.find(x => x.id === id);
if (!s) return;
const vid = pid() + '_s_' + id;
if (s.voters.includes(vid)) return toast('Ты уже голосовал!');
s.stars++; s.voters.push(vid);
SS('swimmers', swimmers);
renderRankings();
toast('Голос учтён!');
}
function voteCoach(id) {
const coaches = LS('coaches') || [];
const c = coaches.find(x => x.id === id);
if (!c) return;
const vid = pid() + '_c_' + id;
if (c.voters.includes(vid)) return toast('Ты уже голосовал!');
c.stars++; c.voters.push(vid);
SS('coaches', coaches);
renderRankings();
toast('Голос учтён!');
}
function renderRankings() {
const swimmers = LS('swimmers') || [];
const coaches = LS('coaches') || [];
const swEl = document.getElementById('swimmer-ranking');
if (!swimmers.length) {
swEl.innerHTML = '<div class="empty"><div class="big">&#x1F3CA;</div>Добавьте спортсменов</div>';
} else {
swEl.innerHTML = [...swimmers].sort((a,b)=>b.stars-a.stars).map((s,i)=>`<div class="card" style="padding:14px;margin-bottom:6px"><div style="display:flex;align-items:center;gap:10px"><div style="width:28px;height:28px;border-radius:50%;background:${i===0?'#FFD700':i===1?'#C0C0C0':i===2?'#CD7F32':'var(--gray-100)'};color:var(--ink);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;flex-shrink:0">${i+1}</div><div style="flex:1"><strong>${s.name}</strong><div style="font-size:11px;color:var(--gray-500)">${[s.club,s.time].filter(Boolean).join(' · ')}</div></div><button class="btn small" onclick="voteSwimmer(${s.id})">&#x2B50; ${s.stars}</button></div></div>`).join('');
}
const coEl = document.getElementById('coach-ranking');
if (!coaches.length) {
coEl.innerHTML = '<div class="empty"><div class="big">&#x1F3CB;</div>Добавьте тренеров</div>';
} else {
coEl.innerHTML = [...coaches].sort((a,b)=>b.stars-a.stars).map((c,i)=>`<div class="card" style="padding:14px;margin-bottom:6px"><div style="display:flex;align-items:center;gap:10px"><div style="width:28px;height:28px;border-radius:50%;background:${i===0?'#FFD700':i===1?'#C0C0C0':i===2?'#CD7F32':'var(--gray-100)'};color:var(--ink);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;flex-shrink:0">${i+1}</div><div style="flex:1"><strong>${c.name}</strong><div style="font-size:11px;color:var(--gray-500)">${[c.club,c.exp?c.exp+' лет':null,c.achieve].filter(Boolean).join(' · ')}</div></div><button class="btn small" onclick="voteCoach(${c.id})">&#x2B50; ${c.stars}</button></div></div>`).join('');
}
}
// PWA
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').catch(function(){}); }
// === LESSONS ===
const lessons = [
{ title: 'Техника старта с тумбы', q: 'swimming start technique tutorial' },
{ title: 'Поворот кувырком', q: 'flip turn swimming technique' },
{ title: 'Гребок и пронос в кроле', q: 'freestyle stroke technique fast' },
{ title: 'Дыхание в кроле', q: 'freestyle breathing technique' },
{ title: 'Дельфиньи удары под водой', q: 'underwater dolphin kick technique' },
{ title: 'Брасс: техника', q: 'breaststroke technique tutorial' },
{ title: 'Калеб Дрессел — анализ', q: 'Caeleb Dressel 50m freestyle analysis' },
{ title: 'Растяжка для пловцов', q: 'swimming stretching routine' },
{ title: 'Силовые на суше', q: 'dryland training swimming' },
{ title: 'Психология старта', q: 'swimming race psychology preparation' },
{ title: 'Питание пловца', q: 'swimmer nutrition meal plan' },
{ title: 'Восстановление после тренировки', q: 'swimming recovery routine' }
];
document.getElementById('lesson-list').innerHTML = lessons.map(l =>
`<div class="card" style="padding:14px;margin-bottom:6px"><strong>${l.title}</strong><div style="font-size:12px;color:var(--gray-500);margin-top:4px">YouTube: <em>${l.q}</em></div></div>`
).join('');
// === RENDER ALL ===
function renderAll() {
renderDiary();
renderVitamins();
renderSleep();
renderTests();
renderVideos();
renderPhotos();
renderGrades();
renderRankings();
}
// === INIT ===
(function init() {
const profiles = LS('profiles') || [];
renderProfileList();
if (profiles.length > 0) {
// Auto-login last profile
loginProfile(profiles[profiles.length - 1].id);
}
document.getElementById('diary-date').value = new Date().toISOString().slice(0,10);
})();
</script>
</body>
</html>