Files
e2c-how/layouts/_default/list.html
2025-07-20 00:06:09 -06:00

359 lines
10 KiB
HTML

{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ if .Description }}
<p class="lead">{{ .Description }}</p>
{{ end }}
{{ .Content }}
{{ if .Pages }}
<div class="filter-controls">
<div class="filter-row">
<input type="text" id="searchInput" placeholder="Search titles and descriptions..." class="search-input">
<select id="tagFilter" class="tag-filter">
<option value="">All tags</option>
</select>
<select id="sortBy" class="sort-select">
<option value="title">Sort by Title</option>
<option value="date">Sort by Date</option>
</select>
</div>
</div>
<div class="grid" id="cardGrid">
{{ range .Pages }}
<div class="card"
data-title="{{ .Title }}"
data-description="{{ if .Description }}{{ .Description }}{{ else if .Summary }}{{ .Summary | plainify | replaceRE "^### \\*\\*Summary\\*\\*\\s*" "" | truncate 200 }}{{ else }}{{ .Content | plainify | replaceRE "^### \\*\\*Summary\\*\\*\\s*" "" | truncate 200 }}{{ end }}"
data-tags="{{ if .Params.tags }}{{ delimit .Params.tags "," }}{{ end }}"
data-date="{{ .Date.Format "2006-01-02" }}">
{{ if .Params.banner }}
<a href="{{ .RelPermalink }}" class="card-banner-link">
<div class="card-banner">
<img src="{{ .Params.banner }}" alt="{{ .Title }} banner" class="case-banner" />
{{ if .Params.image }}
<img src="{{ .Params.image }}" alt="{{ .Title }} logo" class="case-logo-overlay" />
{{ end }}
</div>
</a>
{{ else if .Params.image }}
<a href="{{ .RelPermalink }}" class="card-logo-link">
<img src="{{ .Params.image }}" alt="{{ .Title }} logo" class="case-logo" />
</a>
{{ end }}
<h3><a href="{{ .RelPermalink }}" class="card-title-link">{{ .Title }}</a></h3>
{{ if .Description }}
<p>{{ .Description }}</p>
{{ else if .Summary }}
<p>{{ .Summary | plainify | replaceRE "^### \\*\\*Summary\\*\\*\\s*" "" | truncate 200 }}</p>
{{ else }}
<p>{{ .Content | plainify | replaceRE "^### \\*\\*Summary\\*\\*\\s*" "" | truncate 200 }}</p>
{{ end }}
{{ if .Params.tags }}
<div class="tags">
{{ range .Params.tags }}
<a href="?tag={{ . | urlize }}" class="tag">{{ . }}</a>
{{ end }}
</div>
{{ end }}
</div>
{{ end }}
</div>
{{ end }}
<style>
.lead {
font-size: 1.125rem;
color: #666;
margin-bottom: 2rem;
}
.filter-controls {
margin-bottom: 48px;
padding: 24px;
background: var(--card-background);
border-radius: 16px;
border: 1px solid var(--border);
box-shadow: 0 4px 12px var(--shadow);
}
.filter-row {
display: flex;
gap: 16px;
flex-wrap: wrap;
align-items: center;
}
.search-input, .tag-filter, .sort-select {
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-weight: 500;
font-size: 14px;
padding: 10px 16px;
border: 1px solid var(--border);
border-radius: 24px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(8px);
transition: all 0.2s ease;
color: var(--text-primary);
}
.search-input {
flex: 1;
min-width: 280px;
}
.search-input:focus, .tag-filter:focus, .sort-select:focus {
outline: none;
border-color: var(--e2c-yellow);
background: var(--e2c-yellow);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.search-input::placeholder {
color: var(--text-secondary);
font-weight: 400;
}
.tag-filter, .sort-select {
min-width: 160px;
}
.tags {
margin-top: 0.5rem;
}
.tag {
display: inline-block;
background: #e9ecef;
color: #495057;
padding: 0.25rem 0.5rem;
margin: 0.25rem 0.25rem 0 0;
border-radius: 0.25rem;
font-size: 0.875rem;
text-decoration: none;
transition: all 0.2s ease;
}
.tag:hover {
background: var(--e2c-yellow);
color: var(--text-primary);
transform: translateY(-1px);
}
.card-title-link {
display: inline-block;
padding: 8px 16px;
background: var(--e2c-yellow);
color: var(--text-primary) !important;
text-decoration: none !important;
border-radius: 12px;
margin: 8px 0;
font-weight: 600;
font-size: 1.25rem;
transition: all 0.2s ease;
border: 2px solid var(--e2c-yellow);
}
.card-title-link:hover {
background: var(--e2c-dark-yellow);
border-color: var(--e2c-dark-yellow);
color: var(--text-primary) !important;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.card {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.card.hidden {
display: none;
}
.card-banner {
position: relative;
margin-bottom: 1rem;
border-radius: 12px;
overflow: hidden;
aspect-ratio: 16/9;
}
.case-banner {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.card-banner-link, .card-logo-link {
display: block;
text-decoration: none;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card-banner-link:hover, .card-logo-link:hover {
transform: translateY(-2px);
}
.card-banner-link:hover .card-banner {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
.card-logo-link:hover .case-logo {
transform: scale(1.05);
}
@media (max-width: 768px) {
.filter-controls {
margin-bottom: 32px;
padding: 16px;
}
.filter-row {
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.search-input, .tag-filter, .sort-select {
width: 100%;
min-width: unset;
padding: 8px 14px;
font-size: 12px;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput');
const tagFilter = document.getElementById('tagFilter');
const sortBy = document.getElementById('sortBy');
const cardGrid = document.getElementById('cardGrid');
const cards = Array.from(cardGrid.querySelectorAll('.card'));
// Check for URL parameter to pre-filter by tag
const urlParams = new URLSearchParams(window.location.search);
const preSelectedTag = urlParams.get('tag');
// Collect all unique tags
const allTags = new Set();
cards.forEach(card => {
const tags = card.dataset.tags;
if (tags) {
tags.split(',').forEach(tag => allTags.add(tag.trim()));
}
});
// Populate tag filter dropdown
Array.from(allTags).sort().forEach(tag => {
const option = document.createElement('option');
option.value = tag;
option.textContent = tag;
if (preSelectedTag && tag === preSelectedTag) {
option.selected = true;
}
tagFilter.appendChild(option);
});
function updateTagFilter() {
// Store currently selected value
const currentSelection = tagFilter.value;
// Clear existing options except "All tags"
tagFilter.innerHTML = '<option value="">All tags</option>';
// Collect tags from currently visible cards
const visibleTags = new Set();
cards.forEach(card => {
if (!card.classList.contains('hidden')) {
const tags = card.dataset.tags;
if (tags) {
tags.split(',').forEach(tag => visibleTags.add(tag.trim()));
}
}
});
// Populate tag filter dropdown with current tags
Array.from(visibleTags).sort().forEach(tag => {
const option = document.createElement('option');
option.value = tag;
option.textContent = tag;
// Maintain selection if this tag was previously selected
if (tag === currentSelection) {
option.selected = true;
}
tagFilter.appendChild(option);
});
}
function filterAndSort() {
const searchTerm = searchInput.value.toLowerCase();
const selectedTag = tagFilter.value;
const sortCriteria = sortBy.value;
// Filter cards
const filteredCards = cards.filter(card => {
const title = card.dataset.title.toLowerCase();
const description = card.dataset.description.toLowerCase();
const tags = card.dataset.tags;
// Text search
const matchesSearch = !searchTerm ||
title.includes(searchTerm) ||
description.includes(searchTerm);
// Tag filter
const matchesTag = !selectedTag ||
(tags && tags.split(',').map(t => t.trim()).includes(selectedTag));
return matchesSearch && matchesTag;
});
// Sort filtered cards
filteredCards.sort((a, b) => {
if (sortCriteria === 'title') {
return a.dataset.title.localeCompare(b.dataset.title);
} else if (sortCriteria === 'date') {
const dateA = new Date(a.dataset.date || '1970-01-01');
const dateB = new Date(b.dataset.date || '1970-01-01');
return dateB - dateA; // Most recent first
}
return 0;
});
// Hide all cards first
cards.forEach(card => card.classList.add('hidden'));
// Show and reorder filtered cards
filteredCards.forEach((card, index) => {
card.classList.remove('hidden');
card.style.order = index;
});
// Update tag filter to show only current tags
updateTagFilter();
}
// Event listeners
searchInput.addEventListener('input', filterAndSort);
tagFilter.addEventListener('change', function() {
// Update URL when tag filter changes
const selectedTag = tagFilter.value;
const url = new URL(window.location);
if (selectedTag) {
url.searchParams.set('tag', selectedTag);
} else {
url.searchParams.delete('tag');
}
window.history.replaceState({}, '', url);
filterAndSort();
});
sortBy.addEventListener('change', filterAndSort);
// Initial sort by title
filterAndSort();
});
</script>
{{ end }}