]> danix's work - danix.xyz-2.git/commitdiff
docs: add search functionality design spec
authorDanilo M. <redacted>
Mon, 20 Apr 2026 11:08:21 +0000 (13:08 +0200)
committerDanilo M. <redacted>
Mon, 20 Apr 2026 11:08:21 +0000 (13:08 +0200)
Comprehensive design for unified search across desktop header (modal), mobile menu (inline), and 404 page. Includes lazy-loaded JSON index, Alpine.js components, i18n integration, and WCAG 2.1 AA compliance requirements.

Co-Authored-By: Claude Haiku 4.5 <redacted>
docs/superpowers/specs/2026-04-20-search-functionality-design.md [new file with mode: 0644]

diff --git a/docs/superpowers/specs/2026-04-20-search-functionality-design.md b/docs/superpowers/specs/2026-04-20-search-functionality-design.md
new file mode 100644 (file)
index 0000000..a58526a
--- /dev/null
@@ -0,0 +1,252 @@
+# Search Functionality Design
+**Date:** 2026-04-20  
+**Status:** Design Approved  
+**Scope:** Header + mobile menu search, unified 404 page integration
+
+---
+
+## Overview
+
+Implement a two-context search system for danix.xyz:
+- **Desktop (≥768px):** Hidden search icon in header → overlay modal with search bar + results
+- **Mobile (<768px):** Search bar integrated into hamburger menu overlay (between nav links and language toggle)
+- **Unified backend:** Single lazy-loaded JSON index shared across header search, mobile menu search, and 404 page
+
+---
+
+## Goals
+
+1. Enable users to quickly find articles from any page (especially the 404 page)
+2. Scale efficiently with growing article count (lazy-load index on first interaction)
+3. Maintain clean desktop header aesthetic
+4. Provide seamless mobile experience within existing menu overlay
+5. Follow WCAG 2.1 AA accessibility and project theming standards
+6. Eliminate code duplication between 404 and new search features
+
+---
+
+## Architecture
+
+### Data Layer: Search Index
+
+**Generation:**
+- Hugo builds `/search-index.json` at build time (via `layouts/_default/search-index.json`)
+- Contains all articles from `where .Site.RegularPages "Section" "articles"`
+- Structure per article: `{ title, url, date, summary }`
+- Placed in site root for HTTP access
+
+**Loading:**
+- Fetched on first search interaction (not on page load)
+- Cached in `window.searchIndex` to avoid refetching
+- Handles both languages transparently (index includes all content regardless of lang)
+
+**Index Schema:**
+```json
+[
+  {
+    "title": "Article Title",
+    "url": "/en/articles/slug/",
+    "date": "Jan 02, 2006",
+    "summary": "First 160 chars of article content..."
+  }
+]
+```
+
+---
+
+## Component: Desktop Search Modal
+
+**Trigger:**
+- Search icon (Feather magnifying glass) in header's right control area, next to theme toggle
+- Hidden on mobile (`hidden md:flex`)
+- Click opens full-screen overlay modal
+
+**Modal UI:**
+- Full-screen overlay with semi-transparent backdrop (`bg-black/50`)
+- Centered content box: `max-w-2xl`, `border border-accent`, `rounded-lg`
+- Header: "Search Articles" label (localized via i18n)
+- Search input: `px-4 py-3 border-2 border-border rounded`, focus ring `ring-accent`
+- Results container: scrollable list, max height to prevent overflow
+- Empty state: "No results found" message when query returns nothing
+- Close: Esc key, click outside backdrop, or close button (×)
+
+**Behavior:**
+- Input triggers filtering on keyup (real-time search)
+- Displays max 5 results with title, date, excerpt
+- Each result is a clickable link to the article
+- Result styling: `p-4 border-l-4 border-accent bg-bg/50 hover:bg-bg/70`
+
+**Focus Management (A11y):**
+- Modal receives focus on open (managed by Alpine.js)
+- Focus trap: Tab cycles within modal, cannot escape to background
+- Close button and input are focusable
+- Esc key closes modal gracefully
+
+---
+
+## Component: Mobile Menu Search
+
+**Location:** Inside hamburger overlay, positioned between nav links and language toggle
+**Visibility:** Always visible when menu is open (no extra toggle)
+
+**UI:**
+- Same search input styling as desktop modal
+- Results appear below input as user types
+- Max 5 results, same card layout as desktop
+- Results stack vertically, scrollable if needed
+
+**Behavior:**
+- Shares filtering logic with desktop search
+- Lazy-loads index on first interaction
+- Links are direct navigation (close menu on click if desired—handled by existing menu behavior)
+
+---
+
+## Shared Search Logic: `search.js`
+
+**Location:** `themes/danix-xyz-hacker/assets/js/search.js`
+
+**Responsibilities:**
+1. Load `/search-index.json` on first interaction, cache in `window.searchIndex`
+2. Export `filterArticles(query, articles)` function
+   - Input: lowercase query string, articles array
+   - Output: filtered array (title/summary match), limited to 5 results
+   - Case-insensitive matching on title and summary
+
+**Alpine Components:**
+1. `searchOverlay` (desktop modal)
+   - State: `isOpen`, `searchQuery`, `filteredArticles`, `allArticles`
+   - Methods: `open()`, `close()`, `filterArticles(query)`, `handleEscape()`
+   - Initializer: lazy-loads index on first open
+
+2. `mobileSearch` (hamburger menu integration)
+   - State: `searchQuery`, `filteredArticles`, `allArticles`
+   - Methods: `filterArticles(query)`, `ensureIndexLoaded()`
+   - Initializer: lazy-loads index on menu open (reuses shared load function)
+
+**404 Page Refactor:**
+- Update `notFoundPage` Alpine component to use shared filtering logic
+- Replace inline `window.articlesData` with lazy-loaded `window.searchIndex`
+- Keep existing modal/toggle logic for Easter Egg (unchanged)
+
+---
+
+## i18n Integration
+
+**New Keys (English):** `en.yaml`
+- `searchArticles: "Search Articles"`
+- `searchPlaceholder: "Search by title or content..."`
+- `noSearchResults: "No articles found matching your search."`
+
+**New Keys (Italian):** `it.yaml`
+- `searchArticles: "Cerca Articoli"`
+- `searchPlaceholder: "Cerca per titolo o contenuto..."`
+- `noSearchResults: "Nessun articolo trovato per la tua ricerca."`
+
+---
+
+## Styling & Theme Compliance
+
+**Color Palette:**
+- Text: `text-text` (primary), `text-text-dim` (secondary)
+- Accent: `text-accent` (purple, highlights)
+- Borders: `border-border`
+- Backgrounds: `bg-bg`, `bg-bg/50` (hover), `bg-black/50` (overlay)
+
+**Typography:**
+- Font: JetBrains Mono (monospace throughout)
+- Input focus: `focus:ring-2 focus:ring-accent focus:border-transparent`
+- Results title: `font-bold text-accent`
+- Results date: `text-sm text-text-dim`
+
+**Utilities Only:**
+- No new CSS file
+- All styling via Tailwind utility classes
+- Reuse existing component patterns (buttons, inputs from form-components.html)
+
+---
+
+## Accessibility (WCAG 2.1 AA)
+
+**Contrast:**
+- All text meets ≥4.5:1 contrast ratio
+- Accent text on bg verified
+
+**Keyboard Navigation:**
+- Desktop modal: Esc closes, Tab/Shift+Tab navigates focusable elements
+- Mobile: Input focusable, results clickable
+- Search icon: Keyboard accessible (button element, aria-label)
+
+**Screen Readers:**
+- Modal: `role="dialog"`, `aria-labelledby="search-modal-title"`
+- Input: `<label>` with `for` attribute and `aria-label` fallback
+- Results: Semantic `<a>` links with descriptive text
+- Empty state: `aria-live="polite"` on results container
+
+**Visual Indicators:**
+- Focus states: `ring-2 ring-accent` on inputs
+- Hover states: `hover:bg-bg/70` on result cards
+- Disabled/loading states: TBD if needed (assume instant for now)
+
+---
+
+## Files to Create/Modify
+
+### Create:
+1. `themes/danix-xyz-hacker/layouts/_default/search-index.json` — Hugo template for search index
+2. `themes/danix-xyz-hacker/layouts/partials/search-modal.html` — Desktop modal partial
+3. `themes/danix-xyz-hacker/assets/js/search.js` — Shared search logic
+
+### Modify:
+1. `themes/danix-xyz-hacker/layouts/partials/header.html` — Add search icon trigger
+2. `themes/danix-xyz-hacker/layouts/partials/hamburger-menu.html` — Add mobile search bar
+3. `themes/danix-xyz-hacker/layouts/_default/baseof.html` — Include search-modal partial + search.js bundle
+4. `themes/danix-xyz-hacker/assets/js/not-found-page.js` — Refactor to use shared search logic
+5. `i18n/en.yaml` — Add search labels
+6. `i18n/it.yaml` — Add search labels (Italian)
+
+### No changes needed:
+- `404.en.html` / `404.it.html` — Alpine component behavior unchanged, data source swapped
+
+---
+
+## Testing Strategy
+
+**Manual Testing:**
+1. Desktop: Click search icon → modal opens, input focused, Esc closes
+2. Desktop: Type query → results appear in real-time, max 5 shown
+3. Desktop: Click result → navigates to article
+4. Mobile: Open menu → search bar visible, type query → results appear
+5. Mobile: Click result → navigates to article
+6. 404 page: Search bar works as before (refactored, same UX)
+7. First interaction: Index loads (check Network tab), cached on second interaction
+8. Language switch: Results respect current language links
+
+**A11y Testing:**
+1. Keyboard: Tab navigates modal, Esc closes
+2. Screen reader: Modal announced, input label read, results as links
+3. Focus visible: All interactive elements have clear focus rings
+
+---
+
+## Future Enhancements
+
+- Search result highlighting/excerpts
+- Search analytics (popular searches)
+- Advanced filters (by date, category, type)
+- Search suggestions as-you-type
+- Keyboard shortcut (Cmd+K / Ctrl+K) to open
+
+---
+
+## Success Criteria
+
+- ✅ Header search icon hidden on mobile, visible on desktop
+- ✅ Mobile search integrated into hamburger menu
+- ✅ Search index lazy-loaded (single HTTP request per session)
+- ✅ 404 page search unified with header/menu search
+- ✅ WCAG 2.1 AA compliant
+- ✅ All text localized (EN/IT)
+- ✅ Tailwind utilities only, no new CSS
+- ✅ Keyboard accessible (Esc, Tab, focus management)
+- ✅ Real-time filtering, max 5 results displayed