function el(tag, className, html) { const node = document.createElement(tag); if (className) node.className = className; if (html !== undefined) node.innerHTML = html; return node; } function calcAge(birthDateStr) { const birth = new Date(birthDateStr); const today = new Date(); let age = today.getFullYear() - birth.getFullYear(); const monthDiff = today.getMonth() - birth.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) { age--; } return age; } function pluralizeYears(age) { const mod10 = age % 10; const mod100 = age % 100; if (mod10 === 1 && mod100 !== 11) return `${age} год`; if ([2, 3, 4].includes(mod10) && ![12, 13, 14].includes(mod100)) return `${age} года`; return `${age} лет`; } function renderProfile(data) { document.title = `${data.profile.role} ${data.profile.name}`; document.getElementById("avatar").src = data.profile.avatar; document.getElementById("avatar").alt = data.profile.name; document.getElementById("name").textContent = data.profile.name; document.getElementById("role").textContent = data.profile.role; const metaParts = []; if (data.profile.gender) metaParts.push(data.profile.gender); if (data.profile.birthDate) metaParts.push(pluralizeYears(calcAge(data.profile.birthDate))); if (data.profile.location) metaParts.push(data.profile.location); document.getElementById("meta").textContent = metaParts.join(" · "); const aboutEl = document.getElementById("about"); if (data.profile.about) { aboutEl.textContent = data.profile.about; } else { aboutEl.remove(); } } function renderContacts(data) { const wrap = document.getElementById("contacts"); data.contacts.forEach((c) => { const a = el("a", "contact-icon", ``); a.href = c.href; a.title = c.label; a.target = c.href.startsWith("http") ? "_blank" : "_self"; a.rel = "noopener"; wrap.appendChild(a); }); } function renderSkills(data) { const section = document.getElementById("skills-section"); const hasSkills = data.skills && data.skills.some((g) => g.items && g.items.length); if (!hasSkills) { section.remove(); return; } const wrap = document.getElementById("skills"); data.skills.forEach((group) => { if (!group.items || group.items.length === 0) return; const groupEl = el("div", "skill-group"); groupEl.appendChild(el("div", "skill-category", group.category)); const tags = el("div", "skill-tags"); group.items.forEach((item) => tags.appendChild(el("span", "skill-tag", item))); groupEl.appendChild(tags); wrap.appendChild(groupEl); }); } function renderTimeline(containerId, items) { const wrap = document.getElementById(containerId); items.forEach((item) => { const entry = el("div", "timeline-item" + (item.current ? " is-current" : "")); entry.appendChild(el("div", "timeline-period", item.period)); const body = el("div", "timeline-body"); body.appendChild(el("div", "timeline-title", item.title)); if (item.place) body.appendChild(el("div", "timeline-place", item.place)); entry.appendChild(body); wrap.appendChild(entry); }); } function renderProjects(data) { const wrap = document.getElementById("projects"); data.projects.forEach((p) => { const card = el("a", "project-card"); card.href = p.href; card.target = "_blank"; card.rel = "noopener"; card.appendChild(el("div", "project-title", p.title)); if (p.description) card.appendChild(el("div", "project-desc", p.description)); card.appendChild(el("div", "project-link", "Открыть проект →")); wrap.appendChild(card); }); } (function init() { renderProfile(SITE_DATA); renderContacts(SITE_DATA); renderSkills(SITE_DATA); renderTimeline("education", SITE_DATA.education); renderTimeline("experience", SITE_DATA.experience); renderProjects(SITE_DATA); document.getElementById("footer").textContent = `© ${new Date().getFullYear()} ${SITE_DATA.profile.name}`; })();