initial commit
This commit is contained in:
+29
@@ -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
@@ -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;
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user