115 lines
3.2 KiB
JavaScript
115 lines
3.2 KiB
JavaScript
const projects = window.projects || [];
|
|
const featuredContainer = document.querySelector("#featured-projects");
|
|
const grid = document.querySelector("#project-grid");
|
|
const emptyState = document.querySelector("#empty-state");
|
|
const searchInput = document.querySelector("#project-search");
|
|
const filters = Array.from(document.querySelectorAll(".filter"));
|
|
const projectCount = document.querySelector("#project-count");
|
|
|
|
let activeFilter = "all";
|
|
|
|
projectCount.textContent = projects.length;
|
|
|
|
function formatDate(value) {
|
|
return new Intl.DateTimeFormat("en", {
|
|
month: "short",
|
|
year: "numeric"
|
|
}).format(new Date(`${value}T00:00:00`));
|
|
}
|
|
|
|
function matchesFilter(project) {
|
|
if (activeFilter === "all") return true;
|
|
if (activeFilter === "featured") return project.tags.includes("featured");
|
|
return project.language === activeFilter || project.tags.includes(activeFilter);
|
|
}
|
|
|
|
function matchesSearch(project) {
|
|
const query = searchInput.value.trim().toLowerCase();
|
|
if (!query) return true;
|
|
|
|
const haystack = [
|
|
project.name,
|
|
project.description,
|
|
project.siteUrl,
|
|
project.language,
|
|
project.license,
|
|
project.tags.join(" ")
|
|
].join(" ").toLowerCase();
|
|
|
|
return haystack.includes(query);
|
|
}
|
|
|
|
function metric(label, value) {
|
|
return `<span><strong>${value}</strong>${label}</span>`;
|
|
}
|
|
|
|
function tags(project) {
|
|
return project.tags
|
|
.filter((tag) => tag !== "featured")
|
|
.slice(0, 3)
|
|
.map((tag) => `<span class="tag">${tag}</span>`)
|
|
.join("");
|
|
}
|
|
|
|
function actions(project) {
|
|
const liveLink = project.siteUrl
|
|
? `<a class="project-link live" href="${project.siteUrl}">Launch</a>`
|
|
: "";
|
|
|
|
return `
|
|
<span class="project-actions">
|
|
${liveLink}
|
|
<a class="project-link" href="${project.url}">GitHub</a>
|
|
</span>
|
|
`;
|
|
}
|
|
|
|
function card(project, size = "standard") {
|
|
return `
|
|
<article class="project-card ${size}" style="--accent: ${project.accent}">
|
|
<div class="project-accent" aria-hidden="true"></div>
|
|
<div class="project-body">
|
|
<div class="project-title-row">
|
|
<h3>${project.name}</h3>
|
|
<span class="language">${project.language || "Code"}</span>
|
|
</div>
|
|
<p>${project.description}</p>
|
|
<div class="tag-row">${tags(project)}</div>
|
|
</div>
|
|
<footer class="project-meta">
|
|
${metric("stars", project.stars)}
|
|
${metric("forks", project.forks)}
|
|
<span>Updated ${formatDate(project.updated)}</span>
|
|
${actions(project)}
|
|
</footer>
|
|
</article>
|
|
`;
|
|
}
|
|
|
|
function renderFeatured() {
|
|
featuredContainer.innerHTML = projects
|
|
.filter((project) => project.tags.includes("featured"))
|
|
.map((project) => card(project, "featured-card"))
|
|
.join("");
|
|
}
|
|
|
|
function renderProjects() {
|
|
const visible = projects.filter((project) => matchesFilter(project) && matchesSearch(project));
|
|
grid.innerHTML = visible.map((project) => card(project)).join("");
|
|
emptyState.hidden = visible.length > 0;
|
|
}
|
|
|
|
filters.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
filters.forEach((filter) => filter.classList.remove("active"));
|
|
button.classList.add("active");
|
|
activeFilter = button.dataset.filter;
|
|
renderProjects();
|
|
});
|
|
});
|
|
|
|
searchInput.addEventListener("input", renderProjects);
|
|
|
|
renderFeatured();
|
|
renderProjects();
|