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}`;
})();