# 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: `