(function () { "use strict"; function escapeHtml(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function getPageKey() { return document.body.getAttribute("data-page") || ""; } function linkTarget(link) { if (link && link.newTab === false) { return ""; } return ' target="_blank" rel="noopener"'; } function renderInlineLinks(links, separator) { if (!Array.isArray(links) || links.length === 0) { return ""; } return links .map(function (link) { return ( '" + (link.label || "") + "" ); }) .join(typeof separator === "string" ? separator : " / "); } function renderNav(data, pageKey) { var nav = document.querySelector('[data-component="nav"]'); if (!nav || !data || !data.site || !Array.isArray(data.site.navLinks)) { return; } var navName = pageKey === "home" ? data.site.homeNavName : data.site.navName; var navLinksHtml = data.site.navLinks .map(function (link) { var current = link.key === pageKey ? ' aria-current="page"' : ""; return ( '" + (link.label || "") + "" ); }) .join(""); nav.innerHTML = '"; } function renderFooter(data, pageKey) { var footers = document.querySelectorAll('footer[data-component="footer"]'); if (!footers.length || !data || !data.site) { return; } var pageLastUpdated = pageKey === "home" && data.home ? data.home.lastUpdated || "" : ""; footers.forEach(function (footer) { var lastUpdatedAttr = footer.getAttribute("data-last-updated"); var lastUpdatedText = pageLastUpdated; if (lastUpdatedAttr !== null) { lastUpdatedText = lastUpdatedAttr; } footer.innerHTML = "

" + "© " + escapeHtml(data.site.fullName) + (lastUpdatedText ? " · Last updated " + escapeHtml(lastUpdatedText) : "") + "

"; }); } function setCurrentYear() { var yearElements = document.querySelectorAll("[data-year]"); var year = new Date().getFullYear(); yearElements.forEach(function (element) { element.textContent = String(year); }); } function renderHome(data) { if (!data || !data.home) { return; } var publicationTarget = document.getElementById("home-publications-list"); if (publicationTarget && Array.isArray(data.home.publications)) { publicationTarget.innerHTML = data.home.publications .map(function (publication) { return ( '
' + '
' + '' +
            escapeHtml(publication.alt) +
            '' + "
" + '
' + '' + (publication.title || "") + "
" + (publication.authorsHtml || "") + "
" + "" + escapeHtml(publication.venue) + "
" + '

' + escapeHtml(publication.description) + "

" + renderInlineLinks(publication.links, " / ") + "
" + "
" ); }) .join(""); } var educationTarget = document.getElementById("home-education-list"); if (educationTarget && Array.isArray(data.home.education)) { educationTarget.innerHTML = data.home.education .map(function (entry) { return ( '
' + '
' + '' + (entry.title || "") + "" + '" + "
" + '
' + (entry.organization || "") + "
" + "
" ); }) .join(""); } var selectedProjectsTarget = document.getElementById( "home-selected-projects-list" ); if (selectedProjectsTarget && Array.isArray(data.home.selectedProjects)) { selectedProjectsTarget.innerHTML = data.home.selectedProjects .map(function (project) { return ( '
' + '
' + '' + (project.title || "") + "" + "
" + "

" + escapeHtml(project.summary) + "

" + '

' + renderInlineLinks(project.links, " / ") + "

" + "
" ); }) .join(""); } } function renderProject(project) { var tags = Array.isArray(project.tags) ? project.tags : []; var tagHtml = tags .map(function (tag) { return '' + tag + ""; }) .join(""); var imageHtml = ""; if (project.image) { imageHtml = '
' + '' +
        escapeHtml(project.imageAlt || project.title || ' + "
"; } var paragraphs = Array.isArray(project.paragraphsHtml) ? project.paragraphsHtml : []; var paragraphHtml = paragraphs .map(function (paragraph) { return "

" + paragraph + "

"; }) .join(""); var tech = Array.isArray(project.tech) ? project.tech : []; var techHtml = ""; if (tech.length) { techHtml = '"; } var linkHtml = ""; if (Array.isArray(project.links) && project.links.length) { var prefix = project.linkPrefix ? project.linkPrefix + " " : ""; var separator = typeof project.linkSeparator === "string" ? project.linkSeparator : " / "; linkHtml = '"; } return ( '
' + '
' + "

" + (project.title || "") + "

" + (tagHtml ? '' + tagHtml + "" : "") + "
" + '
' + imageHtml + '
' + paragraphHtml + techHtml + linkHtml + "
" + "
" + "
" ); } function renderProjectsPage(data) { if (!data || !data.projectsPage || !Array.isArray(data.projectsPage.sections)) { return; } var target = document.getElementById("projects-sections"); if (!target) { return; } target.innerHTML = data.projectsPage.sections .map(function (section) { var projects = Array.isArray(section.projects) ? section.projects : []; return ( '
' + "

" + (section.title || "") + "

" + projects.map(renderProject).join("") + "
" ); }) .join(""); } function renderPublication(publication) { return ( '
' + '
' + '' +
      escapeHtml(publication.alt) +
      '' + "
" + '
' + '' + (publication.title || "") + "
" + (publication.authorsHtml || "") + "
" + "" + escapeHtml(publication.venue) + "" + '

' + escapeHtml(publication.description) + "

" + '" + "
" + "
" ); } function renderPublicationsPage(data) { if ( !data || !data.publicationsPage || !Array.isArray(data.publicationsPage.sections) ) { return; } var target = document.getElementById("publications-sections"); if (!target) { return; } target.innerHTML = data.publicationsPage.sections .map(function (section) { var publications = Array.isArray(section.publications) ? section.publications : []; return ( '
' + "

" + (section.title || "") + "

" + publications.map(renderPublication).join("") + "
" ); }) .join(""); } function renderCvPage(data) { if (!data || !data.cvPage) { return; } var cvData = data.cvPage; var educationTarget = document.getElementById("cv-education-list"); if (educationTarget && Array.isArray(cvData.education)) { educationTarget.innerHTML = cvData.education .map(function (entry) { return ( '
' + '
' + '' + (entry.title || "") + "" + '" + "
" + '
' + (entry.organizationHtml || entry.organization || "") + "
" + "

" + (entry.descriptionHtml || "") + "

" + "
" ); }) .join(""); } var experienceTarget = document.getElementById("cv-experience-list"); if (experienceTarget && Array.isArray(cvData.experience)) { experienceTarget.innerHTML = cvData.experience .map(function (entry) { var bullets = Array.isArray(entry.bullets) ? entry.bullets : []; return ( '
' + '
' + '' + (entry.title || "") + "" + '" + "
" + '
' + (entry.organizationHtml || entry.organization || "") + "
" + "" + "
" ); }) .join(""); } var skillsTarget = document.getElementById("cv-skills-table"); if (skillsTarget && Array.isArray(cvData.skills)) { skillsTarget.innerHTML = cvData.skills .map(function (skill) { return ( "" + '' + (skill.label || "") + "" + "" + (skill.value || "") + "" + "" ); }) .join(""); } var awardsTarget = document.getElementById("cv-awards-list"); if (awardsTarget && Array.isArray(cvData.awards)) { awardsTarget.innerHTML = cvData.awards .map(function (award) { return "
  • " + award + "
  • "; }) .join(""); } var certsTarget = document.getElementById("cv-certifications-list"); if (certsTarget && Array.isArray(cvData.certifications)) { certsTarget.innerHTML = cvData.certifications .map(function (certification) { return "
  • " + certification + "
  • "; }) .join(""); } var contactTarget = document.getElementById("cv-contact"); if (contactTarget && cvData.contactHtml) { contactTarget.innerHTML = cvData.contactHtml; } } function syncCanonicalUrl() { var canonical = document.querySelector('link[rel="canonical"]'); if (!canonical) { return; } var canonicalUrl = window.location.origin + window.location.pathname; canonical.setAttribute("href", canonicalUrl); var ogUrl = document.querySelector('meta[property="og:url"]'); if (ogUrl) { ogUrl.setAttribute("content", canonicalUrl); } } function renderPageSections(data, pageKey) { if (pageKey === "home") { renderHome(data); return; } if (pageKey === "projects") { renderProjectsPage(data); return; } if (pageKey === "publications") { renderPublicationsPage(data); return; } if (pageKey === "cv") { renderCvPage(data); } } function init() { var pageKey = getPageKey(); syncCanonicalUrl(); fetch("assets/data/content.json") .then(function (response) { if (!response.ok) { throw new Error("Failed to load portfolio content data."); } return response.json(); }) .then(function (data) { renderNav(data, pageKey); renderFooter(data, pageKey); renderPageSections(data, pageKey); setCurrentYear(); }) .catch(function (error) { setCurrentYear(); console.error(error); }); } document.addEventListener("DOMContentLoaded", init); })();