initial commit

This commit is contained in:
2026-04-14 16:20:22 +03:00
commit 724f16b589
39 changed files with 1179 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
let stackList = [];
let projectsList = [];
let linksList = [];
const fetchJson = async (path) => {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`Failed to load ${path}: ${response.status}`);
}
return response.json();
};
const loadIcon = async (key) => {
const response = await fetch(`./icons/${key}.svg`);
if (!response.ok) {
return key;
}
return response.text();
};
const formatIndex = (index) => String(index + 1).padStart(2, "0");
document.addEventListener("DOMContentLoaded", () => {
loadStack().then(loadProjects).then(loadLinks).catch(console.error);
});
+48
View File
@@ -0,0 +1,48 @@
const loadLinks = async () => {
const linkContainer = document.getElementById("links");
if (!linkContainer) {
return;
}
linksList = await fetchJson("./data/links.json");
const linkCards = linksList.map(buildLinkCard);
linkContainer.replaceChildren(...linkCards);
};
const buildLinkCard = (item, index) => {
const card = document.createElement("a");
card.classList.add("link-card", `tone-${item.tone ?? "mint"}`);
card.href = item.url;
card.target = "_blank";
card.rel = "noreferrer";
const indexBadge = document.createElement("span");
indexBadge.classList.add("link-card__index");
indexBadge.textContent = formatIndex(index);
const badge = document.createElement("span");
badge.classList.add("link-card__badge");
badge.textContent = item.key;
loadIcon(item.key).then((svg) => (badge.innerHTML = svg));
const body = document.createElement("span");
body.classList.add("link-card__body");
const name = document.createElement("span");
name.classList.add("link-card__name");
name.textContent = item.name;
const label = document.createElement("span");
label.classList.add("link-card__label");
label.textContent = item.label;
const arrow = document.createElement("span");
arrow.classList.add("link-card__arrow");
arrow.textContent = "open";
body.append(name, label);
card.append(indexBadge, badge, body, arrow);
return card;
};
+94
View File
@@ -0,0 +1,94 @@
const loadProjects = async () => {
const projectContainer = document.getElementById("projects");
if (!projectContainer) {
return;
}
projectsList = await fetchJson("./data/projects.json");
if (projectsList.length === 0) {
const empty = document.createElement("p");
empty.classList.add("project-empty");
empty.textContent = "Projects are taking shape.";
projectContainer.replaceChildren(empty);
return;
}
const projectCards = projectsList.map(buildProjectCard);
projectContainer.replaceChildren(...projectCards);
};
const buildProjectCard = (project, index) => {
const card = document.createElement("article");
card.classList.add("project-card", `project-card--${project.tone ?? "mint"}`);
const header = document.createElement("div");
header.classList.add("project-card__header");
const indexBadge = document.createElement("span");
indexBadge.classList.add("project-card__index");
indexBadge.textContent = formatIndex(index);
const key = document.createElement("span");
key.classList.add("project-card__key");
key.textContent = project.key;
const status = document.createElement("span");
status.classList.add("project-card__status");
status.textContent = project.status ?? "project";
const title = document.createElement("h3");
title.classList.add("project-card__title");
title.textContent = project.name;
const description = document.createElement("p");
description.classList.add("project-card__description");
description.textContent = project.description;
const stack = document.createElement("div");
stack.classList.add("project-card__stack");
if (project.stack) {
const badgeToStack = loadStackForProject(project.stack);
stack.append(
...project.stack.map((item) => {
const chip = document.createElement("span");
const stackItem = badgeToStack[item] ?? {
name: item,
tone: "blue",
};
chip.textContent = stackItem.name;
chip.classList.add(
"project-card__stack-chip",
`tone-${stackItem?.tone ?? "mint"}`,
);
return chip;
}),
);
}
header.append(indexBadge, key, status);
card.append(header, title, description, stack);
if (project.links) {
const links = document.createElement("div");
links.classList.add("project-card__links");
links.append(
...Object.entries(project.links).map(([label, url]) => {
const link = document.createElement("a");
link.href = url;
link.textContent = label;
link.target = "_blank";
link.rel = "noreferrer";
return link;
}),
);
card.append(links);
}
return card;
};
+75
View File
@@ -0,0 +1,75 @@
const getExperienceLevel = (years) => {
if (years >= 7) return "Core";
if (years >= 4) return "Strong";
return "Growing";
};
const loadStackForProject = (keys) => {
const stacks = stackList.filter((stack) => keys.includes(stack.key));
const output = {};
stacks.forEach((stack) => {
output[stack.key] = stack;
});
return output;
};
const loadStack = async () => {
const stackContainer = document.getElementById("stack");
if (!stackContainer) {
return;
}
stackList = await fetchJson("./data/stack.json");
const stackCards = stackList.map(buildStackCard).filter(Boolean);
stackContainer.replaceChildren(...stackCards);
};
const buildStackCard = (stack) => {
if (stack?.hide === true) {
return null;
}
const card = document.createElement("div");
card.classList.add("stack-card", `tone-${stack.tone ?? "mint"}`);
const badge = document.createElement("div");
badge.classList.add("stack-card__badge");
badge.textContent = stack.key;
loadIcon(stack.key).then((svg) => (badge.innerHTML = svg));
const body = document.createElement("div");
body.classList.add("stack-card__body");
const eyebrow = document.createElement("p");
eyebrow.classList.add("stack-card__eyebrow");
eyebrow.textContent = getExperienceLevel(stack.years);
const name = document.createElement("h3");
name.classList.add("stack-card__title");
name.textContent = stack.name;
const footer = document.createElement("div");
footer.classList.add("stack-card__footer");
const years = document.createElement("span");
years.textContent = `${stack.years}+ years`;
footer.append(years);
if (stack.tags) {
footer.append(
...stack.tags.map((tag) => {
const badge = document.createElement("span");
badge.textContent = tag;
return badge;
}),
);
}
body.append(eyebrow, name, footer);
card.append(badge, body);
return card;
};