Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
507 changes: 447 additions & 60 deletions assets/css/style.css

Large diffs are not rendered by default.

188 changes: 99 additions & 89 deletions assets/js/search.js
Original file line number Diff line number Diff line change
@@ -1,113 +1,118 @@
// Simple client-side search functionality
// Pagefind search integration
(function() {
'use strict';

let searchIndex = [];
let pagefind;
let pagefindLoaded = false;
let searchModal, searchInput, searchResults, searchClose;

// Simple fuzzy search function
function fuzzySearch(query, text) {
if (!query || !text) return false;
// Lazy load Pagefind only when needed
async function ensurePagefind() {
if (pagefindLoaded) return;

query = query.toLowerCase();
text = text.toLowerCase();

// Direct match gets highest priority
if (text.includes(query)) return true;

// Fuzzy match - all query chars appear in order
let queryIndex = 0;
for (let i = 0; i < text.length && queryIndex < query.length; i++) {
if (text[i] === query[queryIndex]) {
queryIndex++;
}
try {
pagefind = await import('/pagefind/pagefind.js');
pagefindLoaded = true;
} catch (error) {
console.error('Error loading Pagefind:', error);
throw error;
}
return queryIndex === query.length;
}

// Search function
function performSearch(query) {
// Perform search with Pagefind
async function performSearch(query) {
if (!query || query.length < 2) {
searchResults.innerHTML = '<div class="search-no-results"><div class="search-no-results-icon">🔍</div><p>Start typing to search posts...</p></div>';
return;
}

const results = searchIndex.filter(post => {
return fuzzySearch(query, post.title) ||
fuzzySearch(query, post.description) ||
fuzzySearch(query, post.content) ||
(post.tags && post.tags.some(tag => fuzzySearch(query, tag))) ||
(post.categories && post.categories.some(cat => fuzzySearch(query, cat)));
});
// Show loading state immediately
searchResults.innerHTML = '<div class="search-no-results"><div class="search-no-results-icon">⏳</div><p>Searching...</p></div>';

if (results.length === 0) {
searchResults.innerHTML = '<div class="search-no-results"><div class="search-no-results-icon">😕</div><p>No posts found matching your search.</p></div>';
return;
try {
// Ensure Pagefind is loaded
await ensurePagefind();

const search = await pagefind.search(query);

if (search.results.length === 0) {
searchResults.innerHTML = '<div class="search-no-results"><div class="search-no-results-icon">😕</div><p>No posts found matching your search.</p></div>';
return;
}

// Limit to first 10 results for speed
const limitedResults = search.results.slice(0, 10);

// Load data for limited results
const results = await Promise.all(
limitedResults.map(r => r.data())
);

displayResults(results, search.results.length);
} catch (error) {
console.error('Search error:', error);
searchResults.innerHTML = `<div class="search-no-results"><div class="search-no-results-icon">⚠️</div><p>Unable to load search results. Please check your internet connection or refresh the page and try again.</p>${error && error.message ? `<div class="search-error-details">Error: ${error.message}</div>` : ''}</div>`;
}
}

// Display search results with staggered animation
function displayResults(results, totalCount) {
const resultCount = totalCount > results.length
? `<div class="search-result-count">Showing ${results.length} of ${totalCount} results</div>`
: `<div class="search-result-count">${results.length} result${results.length !== 1 ? 's' : ''}</div>`;

// Clear previous results
searchResults.innerHTML = '';
results.forEach(post => {
const item = document.createElement('a');
item.className = 'search-result-item';
item.href = post.permalink;

const title = document.createElement('h3');
title.className = 'search-result-title';
title.textContent = post.title;
item.appendChild(title);

const meta = document.createElement('div');
meta.className = 'search-result-meta';
meta.textContent = post.date;
item.appendChild(meta);

const desc = document.createElement('p');
desc.className = 'search-result-description';
desc.textContent = post.description || post.content;
item.appendChild(desc);

if (post.tags && post.tags.length > 0) {
const tagsDiv = document.createElement('div');
tagsDiv.className = 'search-result-tags';
post.tags.slice(0, 3).forEach(tag => {
const tagSpan = document.createElement('span');
tagSpan.className = 'search-result-tag';
tagSpan.textContent = tag;
tagsDiv.appendChild(tagSpan);
});
item.appendChild(tagsDiv);
}

searchResults.appendChild(item);
});
const resultsHtml = results.map((result, index) => {
// Extract tags from meta if available
const tags = result.meta.tags ?
result.meta.tags.slice(0, 3).map(tag =>
`<span class="search-result-tag">${tag}</span>`
).join('') : '';

return `
<a href="${result.url}" class="search-result-item" style="animation-delay: ${index * 0.05}s">
<h3 class="search-result-title">${result.meta.title || 'Untitled'}</h3>
<div class="search-result-meta">${result.meta.date || ''}</div>
<p class="search-result-description">${result.excerpt}</p>
${tags ? `<div class="search-result-tags">${tags}</div>` : ''}
</a>
`;
}).join('');

searchResults.innerHTML = resultCount + resultsHtml;
}

// Open search modal
// Open search modal with animation
function openSearch() {
searchModal.style.display = 'flex';
document.body.style.overflow = 'hidden';
setTimeout(() => searchInput.focus(), 100);

// Trigger animation
requestAnimationFrame(() => {
searchModal.classList.add('visible');
});

setTimeout(() => searchInput.focus(), 150);

// Preload Pagefind when modal opens (lazy load)
if (!pagefindLoaded) {
ensurePagefind().catch(err =>
console.error('Failed to preload Pagefind:', err)
);
}
}

// Close search modal
// Close search modal with animation
function closeSearch() {
searchModal.style.display = 'none';
searchModal.classList.remove('visible');

// Wait for animation to complete before hiding
setTimeout(() => {
searchModal.style.display = 'none';
searchInput.value = '';
searchResults.innerHTML = '<div class="search-no-results"><div class="search-no-results-icon">🔍</div><p>Start typing to search posts...</p></div>';
}, 200);

document.body.style.overflow = '';
searchInput.value = '';
searchResults.innerHTML = '';
}

// Load search index
function loadSearchIndex() {
fetch('/index.json')
.then(response => response.json())
.then(data => {
searchIndex = data;
})
.catch(error => {
console.error('Error loading search index:', error);
});
}

// Initialize search
Expand All @@ -116,11 +121,17 @@
searchInput = document.getElementById('search-input');
searchResults = document.getElementById('search-results');
searchClose = document.getElementById('search-close');
const searchTrigger = document.getElementById('search-trigger');

if (!searchModal || !searchInput || !searchResults) return;

// Load search index
loadSearchIndex();
// Keep the hint showing both shortcuts - works for everyone
// No need to change it, the HTML already has both ⌘ K and Ctrl K

// Wire up search trigger button
if (searchTrigger) {
searchTrigger.addEventListener('click', openSearch);
}

// Event listeners
searchClose.addEventListener('click', closeSearch);
Expand All @@ -144,13 +155,13 @@
}
});

// Perform search as user types
// Perform search as user types (reduced debounce for faster response)
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch(e.target.value);
}, 200);
}, 150);
});

// Show initial message
Expand All @@ -165,4 +176,3 @@
}

})();

4 changes: 2 additions & 2 deletions hugo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ enableRobotsTXT = true
enableGitInfo = true
canonifyURLs = true

# Output formats for search
# Output formats (JSON removed - Pagefind handles search indexing)
[outputs]
home = ["HTML", "RSS", "JSON"]
home = ["HTML", "RSS"]
section = ["HTML", "RSS"]

# SEO and Performance
Expand Down
8 changes: 8 additions & 0 deletions hugo_stats.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"html",
"img",
"input",
"kbd",
"li",
"line",
"link",
Expand Down Expand Up @@ -140,10 +141,15 @@
"responsive-image",
"search-close",
"search-header",
"search-hint",
"search-input",
"search-modal",
"search-modal-content",
"search-results",
"search-tooltip",
"search-trigger-icon",
"search-trigger-pill",
"search-trigger-text",
"site-header",
"social-button",
"social-button-linkedin",
Expand Down Expand Up @@ -264,9 +270,11 @@
"saving-our-brains-before-theyre-toast",
"scaling-experience-not-compute",
"search-close",
"search-hint-text",
"search-input",
"search-modal",
"search-results",
"search-trigger",
"so-how-the-heck-do-you-protect-yourself",
"so-what",
"so-what-the-hell-do-we-do",
Expand Down
12 changes: 12 additions & 0 deletions layouts/_default/baseof.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@

<!-- Main site link -->
<a class="main-site-link" href="https://www.nischalskanda.tech" target="_blank" rel="noopener noreferrer">Main Site</a>

<!-- Search trigger pill -->
<button id="search-trigger" class="search-trigger-pill" aria-label="Open search">
<span class="search-trigger-icon">🔍</span>
<span class="search-trigger-text">Search</span>
<span class="search-tooltip">
Press <kbd>⌘ K</kbd> or <kbd>Ctrl K</kbd>
</span>
</button>
</header>

<!-- Search Modal -->
Expand All @@ -62,6 +71,9 @@
</svg>
</button>
</div>
<div class="search-hint">
<span id="search-hint-text">Press <kbd>⌘ K</kbd> or <kbd>Ctrl K</kbd> anywhere to search</span>
</div>
<div id="search-results" class="search-results"></div>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions layouts/_default/list.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{{ define "main" }}
<div data-pagefind-ignore>
<div class="hero-section">
<h1 class="hero-headline">
THE ONLY BLOGS <span class="interactive-o"></span> YOU<br>
Expand Down Expand Up @@ -107,4 +108,5 @@ <h2 class="post-card-title">{{ .Title }}</h2>
</style>
</nav>
{{ end }}
</div>
{{ end }}
2 changes: 1 addition & 1 deletion layouts/_default/single.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{ define "main" }}
<article class="post-content" itemscope itemtype="https://schema.org/BlogPosting">
<article class="post-content" itemscope itemtype="https://schema.org/BlogPosting" data-pagefind-body>
<header class="post-header">
<h1 class="post-title" itemprop="headline">{{ .Title }}</h1>
<div class="post-meta">
Expand Down
2 changes: 1 addition & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"HUGO_VERSION": "0.148.1"
}
},
"buildCommand": "hugo --cleanDestinationDir --minify --baseURL=\"https://blog.nischalskanda.tech/\"",
"buildCommand": "hugo --cleanDestinationDir --minify --baseURL=\"https://blog.nischalskanda.tech/\" && npx --yes [email protected] --site public",
"outputDirectory": "public",
"installCommand": "",
"devCommand": "hugo server --buildDrafts --buildFuture --baseURL=\"http://localhost:1313\"",
Expand Down