// Хакатон Казахтелеком — демо-логика (localStorage, без сервера)
const SEED_PARTICIPANTS = [
{ id: "p1", name: "Айгерим Нурлан", email: "aigerim.nur@example.kz", phone: "+7 777 145 20 18", city: "Астана", direction: "Frontend", team: "solo" },
{ id: "p2", name: "Дмитрий Козлов", email: "d.kozlov@example.com", phone: "+7 701 233 90 41", city: "Алматы", direction: "Backend", team: "team" },
{ id: "p3", name: "Жанара Сәтбаева", email: "janara.sat@example.kz", phone: "+7 747 882 15 07", city: "Караганда", direction: "Data Science", team: "solo" },
{ id: "p4", name: "Ержан Алиев", email: "yerzhan.aliev@example.kz", phone: "+7 705 641 33 82", city: "Астана", direction: "Frontend", team: "team" },
{ id: "p5", name: "Марат Суюнбеков", email: "m.suyunbekov@example.kz", phone: "+7 708 402 77 51", city: "Алматы", direction: "Backend", team: "solo" },
{ id: "p6", name: "Айлин Керимова", email: "ailin.kerimova@example.kz", phone: "+7 745 918 26 44", city: "Астана", direction: "Data Science", team: "team" },
{ id: "p7", name: "Тимур Оспанов", email: "t.ospanov@example.kz", phone: "+7 702 550 19 73", city: "Шымкент", direction: "Frontend", team: "solo" }
];
function loadParticipants() {
try {
const raw = localStorage.getItem("kt_hackathon_v1");
const arr = raw ? JSON.parse(raw) : null;
if (Array.isArray(arr) && arr.length) return arr;
} catch (e) { /* повреждённые данные — пересеваем */ }
localStorage.setItem("kt_hackathon_v1", JSON.stringify(SEED_PARTICIPANTS));
return SEED_PARTICIPANTS.slice();
}
function saveParticipants(list) {
localStorage.setItem("kt_hackathon_v1", JSON.stringify(list));
}
function esc(s) {
return String(s).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
}
const DIR_LABELS = { "Frontend": "Frontend", "Backend": "Backend", "Data Science": "Data Science" };
const DIR_CLASS = { "Frontend": "frontend", "Backend": "backend", "Data Science": "data" };
/* ================= Главный экран: статистика ================= */
function renderStats() {
const el = document.getElementById("stats");
if (!el) return;
const total = loadParticipants().length;
const items = [
{ icon: "calendar", value: "28.09.2026", label: "Дата проведения", hint: "суббота" },
{ icon: "clock", value: "10:00", label: "Начало", hint: "длительность 5 часов" },
{ icon: "building", value: "ул. Сауран, 12", label: "Место проведения", hint: "Казахстан" },
{ icon: "users", value: String(total), label: "Участников", hint: "регистрация открыта" }
];
el.innerHTML = items.map(k =>
'
' +
'' + esc(k.value) + '' +
'' + esc(k.label) + '' +
'' + esc(k.hint) + '' +
'
'
).join("");
}
/* ================= Форма регистрации ================= */
function initRegister() {
const form = document.getElementById("reg-form");
if (!form) return;
function setError(key, msg) {
const el = form.querySelector('[data-err="' + key + '"]');
if (el) el.textContent = msg || "";
}
form.addEventListener("submit", function (ev) {
ev.preventDefault();
const data = {
name: document.getElementById("f-name").value.trim(),
email: document.getElementById("f-email").value.trim(),
phone: document.getElementById("f-phone").value.trim(),
city: document.getElementById("f-city").value.trim() || "—",
direction: document.getElementById("f-dir").value,
team: document.getElementById("f-team").value,
consent: document.getElementById("f-consent").checked
};
const errors = {};
if (data.name.length < 5) errors.name = "Укажите фамилию и имя полностью";
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(data.email)) errors.email = "Укажите корректный email";
if (!/^[+\d][\d\s()-]{9,}$/.test(data.phone)) errors.phone = "Укажите телефон, например +7 700 555 12 34";
if (!data.direction) errors.dir = "Выберите направление";
if (!data.team) errors.team = "Выберите формат участия";
if (!data.consent) errors.consent = "Без согласия мы не можем отправить подтверждение";
Object.keys({ name: 1, email: 1, phone: 1, dir: 1, team: 1, consent: 1 }).forEach(k => setError(k, errors[k]));
if (Object.keys(errors).length) return;
const list = loadParticipants();
if (!list.some(p => p.email.toLowerCase() === data.email.toLowerCase())) {
list.unshift({
id: "r" + Date.now(),
name: data.name,
email: data.email,
phone: data.phone,
city: data.city,
direction: data.direction,
team: data.team
});
saveParticipants(list);
}
const succ = document.getElementById("reg-succ");
document.getElementById("reg-succ-text").textContent =
"Подтверждение отправим на " + data.email + " накануне мероприятия.";
form.hidden = true;
succ.hidden = false;
succ.scrollIntoView({ behavior: "smooth", block: "center" });
});
}
/* ================= Список участников ================= */
let partState = { q: "", dir: "all" };
function initParticipants() {
const tbody = document.getElementById("part-tbody");
if (!tbody) return;
const searchField = document.getElementById("p-search");
const chips = document.getElementById("dir-chips");
const empty = document.getElementById("part-empty");
const strip = document.getElementById("kpi-strip");
function kpiHtml(v, l, h) {
return '' + esc(v) + '' + esc(l) + '' + esc(h) + '
';
}
function render() {
const all = loadParticipants();
// KPI: считаются по текущему фильтру направления
const base = partState.dir === "all" ? all : all.filter(p => p.direction === partState.dir);
const total = base.length;
const solo = base.filter(p => p.team === "solo").length;
const cities = new Set(base.map(p => p.city)).size;
strip.innerHTML =
kpiHtml(String(total), "Участников", partState.dir === "all" ? "всего" : "по направлению") +
kpiHtml(String(solo), "Индивидуально", "остальные — с командами") +
kpiHtml(String(cities), "Городов", "представлены");
// чипы направлений
const dirs = ["all", "Frontend", "Backend", "Data Science"];
chips.innerHTML = dirs.map(d => {
const active = partState.dir === d;
const n = d === "all" ? all.length : all.filter(p => p.direction === d).length;
const label = d === "all" ? "Все" : DIR_LABELS[d];
return '';
}).join("");
// строки
const q = partState.q.toLowerCase();
const rows = all.filter(p =>
(partState.dir === "all" || p.direction === partState.dir) &&
(!q || p.name.toLowerCase().includes(q) || p.city.toLowerCase().includes(q) || p.email.toLowerCase().includes(q))
);
tbody.innerHTML = rows.map(p =>
"" +
"| " + esc(p.name) + '' + esc(p.email) + " | " +
"" + esc(p.city) + " | " +
'' + esc(DIR_LABELS[p.direction] || p.direction) + " | " +
'' + (p.team === "team" ? "команда" : "соло") + " | " +
"
"
).join("");
empty.hidden = rows.length > 0;
}
chips.addEventListener("click", ev => {
const btn = ev.target.closest("[data-dir]");
if (!btn) return;
partState.dir = btn.getAttribute("data-dir");
render();
});
searchField.addEventListener("input", () => {
partState.q = searchField.value.trim();
render();
});
render();
}
/* ================= Старт ================= */
function init() {
if (document.getElementById("reg-form")) {
if (new URLSearchParams(location.search).has("registered")) {
const succ = document.getElementById("reg-succ");
const form = document.getElementById("reg-form");
document.getElementById("reg-succ-text").textContent = "Подтверждение отправим на почту накануне мероприятия.";
form.hidden = true;
succ.hidden = false;
}
}
renderStats();
initRegister();
initParticipants();
}
document.addEventListener("DOMContentLoaded", init);