314 lines
9.2 KiB
HTML
314 lines
9.2 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.image }}
|
|
<img src="{{ .Params.image }}" alt="{{ .Title }} logo" class="case-logo" />
|
|
{{ 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;
|
|
}
|
|
|
|
@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 }} |