diff --git a/projects/index.html b/projects/index.html index 9d4e6dd..100ad86 100644 --- a/projects/index.html +++ b/projects/index.html @@ -5,19 +5,11 @@ --- @@ -240,98 +257,55 @@

Reliable and -
-

Projects

- - +

Projects

-
- -
- - -
-
+
+ All + Projects + Software + +
-
+
{% assign sorted_projects = site.projects | sort: 'date' | reverse %} {% for project in sorted_projects %}
- {% if project.avatar_url %} - {{ project.title }} - {% elsif project.avatar %} - {{ project.title }} - {% endif %} - - {% if project.is_software %} - Software - {% endif %} - - {{ project.title }} - - {% if project.subtitle %} -
{{ project.subtitle }}
- {% endif %} +
+ {% if project.avatar_url %} + + {% elsif project.avatar %} + + {% else %} + + {% endif %} +
- -
- {{ project.content | strip_html }} +
+
+ {{ project.title }} + {% if project.is_software %}Software{% endif %} +
+ {% if project.subtitle %} +
{{ project.subtitle }}
+ {% endif %}
- {% if project.tags %} -
+
{% for tag in project.tags %} - {{ tag }} + {{ tag }} {% endfor %} + {% if project.links.github %} + Code ↗ + {% endif %} + Learn More →
- {% endif %} - - +
{{ project.content | strip_html }}
{% endfor %}
@@ -348,76 +322,57 @@

No projects found

const DEFAULT_VIEW = VIEW_SOFTWARE; const VIEW_CONFIG = { - [VIEW_ALL]: { - heading: 'All Projects and Software', - searchPlaceholder: 'Search all projects and software...', - noResultsTitle: 'No entries found', - noResultsEmpty: 'No projects or software in this category yet', - noResultsSearch: 'Try adjusting your search terms', - docTitle: 'Projects and Software', - metaDescription: 'Browse all DAPLab projects and software releases.' - }, - [VIEW_PROJECTS]: { - heading: 'Projects', - searchPlaceholder: 'Search projects...', - noResultsTitle: 'No projects found', - noResultsEmpty: 'No projects in this category yet', - noResultsSearch: 'Try adjusting your search terms', - docTitle: 'Projects', - metaDescription: 'Browse DAPLab research projects.' - }, - [VIEW_SOFTWARE]: { - heading: 'Software', - searchPlaceholder: 'Search software...', - noResultsTitle: 'No software found', - noResultsEmpty: 'No software in this category yet', - noResultsSearch: 'Try adjusting your search terms', - docTitle: 'Software', - metaDescription: 'Browse DAPLab software releases and open-source tools.' - } + [VIEW_ALL]: { heading: 'All Projects and Software', docTitle: 'Projects and Software', metaDescription: 'Browse all DAPLab projects and software releases.', noResultsTitle: 'No entries found', noResultsEmpty: 'No projects or software in this category yet' }, + [VIEW_PROJECTS]: { heading: 'Projects', docTitle: 'Projects', metaDescription: 'Browse DAPLab research projects.', noResultsTitle: 'No projects found', noResultsEmpty: 'No projects in this category yet' }, + [VIEW_SOFTWARE]: { heading: 'Software', docTitle: 'Software', metaDescription: 'Browse DAPLab software releases and open-source tools.', noResultsTitle: 'No software found', noResultsEmpty: 'No software in this category yet' }, }; -const pageTitle = document.getElementById('projects-page-title'); -const searchInput = document.getElementById('search-input'); -const cards = Array.from(document.querySelectorAll('.projects-grid > .project-card')); -const noResults = document.getElementById('no-results'); +const pageTitle = document.getElementById('projects-page-title'); +const grid = document.getElementById('projects-grid'); +const cards = Array.from(document.querySelectorAll('.projects-grid > .project-card')); +const noResults = document.getElementById('no-results'); const noResultsTitle = noResults.querySelector('h3'); -const noResultsText = noResults.querySelector('p'); +const noResultsText = noResults.querySelector('p'); const viewButtons = Array.from(document.querySelectorAll('.view-toggle-btn')); +const densityBtn = document.getElementById('density-btn'); + const initialDocumentTitle = document.title; -const titleSuffix = initialDocumentTitle.includes(' | ') - ? initialDocumentTitle.slice(initialDocumentTitle.indexOf(' | ')) - : ''; -const metaDescriptionTag = document.querySelector('meta[name="description"]'); -const ogTitleTag = document.querySelector('meta[property="og:title"]'); -const twitterTitleTag = document.querySelector('meta[name="twitter:title"]'); -const ogDescriptionTag = document.querySelector('meta[property="og:description"]'); +const titleSuffix = initialDocumentTitle.includes(' | ') ? initialDocumentTitle.slice(initialDocumentTitle.indexOf(' | ')) : ''; +const metaDescriptionTag = document.querySelector('meta[name="description"]'); +const ogTitleTag = document.querySelector('meta[property="og:title"]'); +const twitterTitleTag = document.querySelector('meta[name="twitter:title"]'); +const ogDescriptionTag = document.querySelector('meta[property="og:description"]'); const twitterDescriptionTag = document.querySelector('meta[name="twitter:description"]'); -const ogUrlTag = document.querySelector('meta[property="og:url"]'); -const canonicalTag = document.querySelector('link[rel="canonical"]'); -const webPageSchemaTag = document.querySelector('script[type="application/ld+json"]'); +const ogUrlTag = document.querySelector('meta[property="og:url"]'); +const canonicalTag = document.querySelector('link[rel="canonical"]'); +const webPageSchemaTag = document.querySelector('script[type="application/ld+json"]'); + +// Density toggle +let cozy = localStorage.getItem('projects-density') === 'cozy'; +function applyDensity() { + grid.classList.toggle('cozy', cozy); + densityBtn.textContent = cozy ? '≡ Compact' : '⊞ Cozy'; +} +densityBtn.addEventListener('click', () => { + cozy = !cozy; + localStorage.setItem('projects-density', cozy ? 'cozy' : 'compact'); + applyDensity(); +}); +applyDensity(); function getViewFromURL() { - const params = new URLSearchParams(window.location.search); - const view = params.get('view'); + const view = new URLSearchParams(window.location.search).get('view'); return isValidView(view) ? view : DEFAULT_VIEW; } -function isValidView(view) { - return view === VIEW_ALL || view === VIEW_PROJECTS || view === VIEW_SOFTWARE; -} +function isValidView(v) { return v === VIEW_ALL || v === VIEW_PROJECTS || v === VIEW_SOFTWARE; } function setActiveView(view, pushState) { const safeView = isValidView(view) ? view : DEFAULT_VIEW; const url = new URL(window.location.href); url.searchParams.set('view', safeView); - - if (pushState) { - window.history.pushState({ view: safeView }, '', url); - } else { - window.history.replaceState({ view: safeView }, '', url); - } - + if (pushState) window.history.pushState({ view: safeView }, '', url); + else window.history.replaceState({ view: safeView }, '', url); updateViewButtons(safeView); updateViewText(safeView); updateMetadata(safeView, url.toString()); @@ -433,131 +388,61 @@

No projects found

} function updateViewText(activeView) { - const viewConfig = VIEW_CONFIG[activeView] || VIEW_CONFIG[DEFAULT_VIEW]; - pageTitle.textContent = viewConfig.heading; - searchInput.placeholder = viewConfig.searchPlaceholder; - noResultsTitle.textContent = viewConfig.noResultsTitle; + const cfg = VIEW_CONFIG[activeView] || VIEW_CONFIG[DEFAULT_VIEW]; + pageTitle.textContent = cfg.heading; + noResultsTitle.textContent = cfg.noResultsTitle; } function updateMetadata(activeView, currentURL) { - const viewConfig = VIEW_CONFIG[activeView] || VIEW_CONFIG[DEFAULT_VIEW]; - const fullTitle = `${viewConfig.docTitle}${titleSuffix}`; + const cfg = VIEW_CONFIG[activeView] || VIEW_CONFIG[DEFAULT_VIEW]; + const fullTitle = `${cfg.docTitle}${titleSuffix}`; document.title = fullTitle; - - if (ogTitleTag) { - ogTitleTag.setAttribute('content', fullTitle); - } - if (twitterTitleTag) { - twitterTitleTag.setAttribute('content', fullTitle); - } - - if (metaDescriptionTag) { - metaDescriptionTag.setAttribute('content', viewConfig.metaDescription); - } - if (ogDescriptionTag) { - ogDescriptionTag.setAttribute('content', viewConfig.metaDescription); - } - if (twitterDescriptionTag) { - twitterDescriptionTag.setAttribute('content', viewConfig.metaDescription); - } - if (ogUrlTag) { - ogUrlTag.setAttribute('content', currentURL); - } - if (canonicalTag) { - canonicalTag.setAttribute('href', currentURL); - } - + if (ogTitleTag) ogTitleTag.setAttribute('content', fullTitle); + if (twitterTitleTag) twitterTitleTag.setAttribute('content', fullTitle); + if (metaDescriptionTag) metaDescriptionTag.setAttribute('content', cfg.metaDescription); + if (ogDescriptionTag) ogDescriptionTag.setAttribute('content', cfg.metaDescription); + if (twitterDescriptionTag) twitterDescriptionTag.setAttribute('content', cfg.metaDescription); + if (ogUrlTag) ogUrlTag.setAttribute('content', currentURL); + if (canonicalTag) canonicalTag.setAttribute('href', currentURL); if (webPageSchemaTag && webPageSchemaTag.textContent) { try { const schema = JSON.parse(webPageSchemaTag.textContent); if (schema && schema['@type'] === 'WebPage') { - schema.name = fullTitle; - schema.url = currentURL; - schema.description = viewConfig.metaDescription; + schema.name = fullTitle; schema.url = currentURL; schema.description = cfg.metaDescription; webPageSchemaTag.textContent = JSON.stringify(schema, null, 2); } - } catch (error) { - // Keep existing structured data when it cannot be safely parsed. - } - } -} - -function matchesActiveView(card, activeView) { - if (activeView === VIEW_ALL) { - return true; + } catch (e) {} } - const isSoftware = card.getAttribute('data-is-software') === 'true'; - const isProject = card.getAttribute('data-is-project') === 'true'; - if (activeView === VIEW_SOFTWARE) { - return isSoftware; - } - return isProject; } function applyFilters(activeView) { const currentView = isValidView(activeView) ? activeView : getViewFromURL(); - const searchTerm = searchInput.value.trim().toLowerCase(); let visibleCount = 0; - cards.forEach(card => { - const inCurrentView = matchesActiveView(card, currentView); - const title = (card.getAttribute('data-title') || '').toLowerCase(); - const tags = (card.getAttribute('data-tags') || '').toLowerCase(); - const authors = (card.getAttribute('data-authors') || '').toLowerCase(); - const content = (card.textContent || '').toLowerCase(); - const matchesSearch = !searchTerm || - title.includes(searchTerm) || - tags.includes(searchTerm) || - authors.includes(searchTerm) || - content.includes(searchTerm); - - if (inCurrentView && matchesSearch) { - card.style.display = 'block'; - visibleCount++; - } else { - card.style.display = 'none'; - } + const isSoftware = card.getAttribute('data-is-software') === 'true'; + const isProject = card.getAttribute('data-is-project') === 'true'; + const visible = currentView === VIEW_ALL + || (currentView === VIEW_SOFTWARE && isSoftware) + || (currentView === VIEW_PROJECTS && isProject); + card.style.display = visible ? '' : 'none'; + if (visible) visibleCount++; }); - if (visibleCount === 0) { noResults.classList.add('show'); - const viewConfig = VIEW_CONFIG[currentView] || VIEW_CONFIG[DEFAULT_VIEW]; - noResultsText.textContent = searchTerm ? viewConfig.noResultsSearch : viewConfig.noResultsEmpty; + noResultsText.textContent = (VIEW_CONFIG[currentView] || VIEW_CONFIG[DEFAULT_VIEW]).noResultsEmpty; } else { noResults.classList.remove('show'); } } -function searchProjects() { - applyFilters(getViewFromURL()); -} - -function resetSearch() { - searchInput.value = ''; - applyFilters(getViewFromURL()); -} - viewButtons.forEach(btn => { - btn.addEventListener('click', function(e) { - e.preventDefault(); - const view = btn.getAttribute('data-view'); - setActiveView(view, true); - }); + btn.addEventListener('click', e => { e.preventDefault(); setActiveView(btn.getAttribute('data-view'), true); }); }); -window.addEventListener('popstate', function() { +window.addEventListener('popstate', () => { const view = getViewFromURL(); - updateViewButtons(view); - updateViewText(view); - updateMetadata(view, window.location.href); - applyFilters(view); -}); - -// Allow Enter key to trigger search -searchInput.addEventListener('keypress', function(e) { - if (e.key === 'Enter') { - searchProjects(); - } + updateViewButtons(view); updateViewText(view); + updateMetadata(view, window.location.href); applyFilters(view); }); setActiveView(getViewFromURL(), false);