First edition
This commit is contained in:
114
app.js
Normal file
114
app.js
Normal file
@@ -0,0 +1,114 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user