summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs/2026-04-20-search-functionality-design.md
blob: a58526a2d6c7c617a0d03ec5c6fbe983195c1bec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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