# SneakerPicks.nl — UX Improvement Plan

> Datum: 20 februari 2026
> Focus: Mobile-first, Nederlandse sneakerheads, conversie-optimalisatie

---

## 1. 🔍 Filter Sidebar — Volledige Redesign

### Huidige problemen
- Filters zijn één lange lijst zonder hiërarchie
- Elke filter-wijziging triggert een full page reload (`window.location.href`)
- Geen maat-filter (cruciaal voor sneakerheads!)
- Geen kleur-filter
- Geen "sale only" toggle
- Mobile: filters openen als inline block, duwt content weg

### Voorgesteld: Collapsible Sections + Smart Defaults

**Volgorde (gebaseerd op hoe sneakerheads zoeken):**

```
1. 🔴 Sale / Korting         ← toggle, sticky bovenaan
2. 👟 Maat (EU)              ← NIEUW, grid van maat-pills
3. 🏷️ Merk                  ← bestaand, verbeterd
4. 👤 Geslacht               ← bestaand
5. 💰 Prijs                  ← bestaand
6. 🎨 Kleur                  ← NIEUW
7. 🏪 Shop                   ← NIEUW
```

### Implementatie-details

**A. Collapsible sections met `<details>`/`<summary>` of React state:**

```tsx
// Elk filter-section:
<div className="border-b border-border/30 pb-4">
  <button 
    onClick={() => toggleSection('maat')}
    className="flex w-full items-center justify-between py-2"
  >
    <h3 className="text-xs font-bold uppercase tracking-widest text-text-muted">
      Maat
    </h3>
    <ChevronIcon className={`h-4 w-4 text-text-muted transition-transform ${open ? 'rotate-180' : ''}`} />
  </button>
  {open && <div className="mt-2">...</div>}
</div>
```

**Default open:** Maat, Merk. **Default dicht:** Kleur, Shop.

**B. Maat-filter (NIEUW — hoogste prioriteit):**

```tsx
// Grid van maat-pills, 5 kolommen
<div className="grid grid-cols-5 gap-1.5">
  {sizes.map(size => (
    <button
      key={size}
      className={`rounded-lg py-2 text-xs font-bold transition-all ${
        selected ? 'bg-accent text-bg' : 'bg-bg-elevated text-text-muted hover:text-text'
      } ${!available ? 'opacity-30 cursor-not-allowed' : ''}`}
    >
      {size}
    </button>
  ))}
</div>
```

Maten: 35.5 t/m 50. Grijs-out maten die 0 resultaten geven.

**C. Kleur-filter (NIEUW):**

```tsx
// Kleurcirkels met label
const colors = [
  { name: 'Zwart', value: 'black', hex: '#111' },
  { name: 'Wit', value: 'white', hex: '#f5f5f5' },
  { name: 'Rood', value: 'red', hex: '#ef4444' },
  // ...
];

<div className="flex flex-wrap gap-2">
  {colors.map(c => (
    <button className="flex flex-col items-center gap-1">
      <div className={`h-7 w-7 rounded-full border-2 ${selected ? 'border-accent' : 'border-transparent'}`}
           style={{ backgroundColor: c.hex }} />
      <span className="text-[10px] text-text-muted">{c.name}</span>
    </button>
  ))}
</div>
```

**D. Sale toggle bovenaan:**

```tsx
<label className="flex items-center gap-3 rounded-xl bg-deal/10 px-4 py-3 cursor-pointer">
  <div className={`h-5 w-10 rounded-full transition-colors ${saleOnly ? 'bg-deal' : 'bg-bg-elevated'}`}>
    <div className={`h-5 w-5 rounded-full bg-white shadow transition-transform ${saleOnly ? 'translate-x-5' : ''}`} />
  </div>
  <span className="text-sm font-bold text-deal">Alleen sale</span>
</label>
```

**E. Client-side filtering (geen page reloads!):**

Huidige situatie: elke filter-wijziging → `window.location.href` → full reload.

**Oplossing:** Gebruik `URL` + `history.pushState` + client-side state. Fetch resultaten via API endpoint (`/api/sneakers?brand=nike&size=42`). Dit maakt filtering instant.

### Mobile Filter: Bottom Sheet

Vervang de huidige inline toggle door een **bottom sheet / drawer**:

```tsx
// Mobile: full-screen overlay van onderaf
<div className="fixed inset-0 z-50 lg:hidden">
  {/* Backdrop */}
  <div className="absolute inset-0 bg-black/60" onClick={close} />
  
  {/* Sheet */}
  <div className="absolute bottom-0 left-0 right-0 max-h-[85vh] overflow-y-auto rounded-t-3xl bg-bg p-6">
    {/* Handle */}
    <div className="mx-auto mb-4 h-1 w-10 rounded-full bg-border/50" />
    
    {/* Header */}
    <div className="mb-6 flex items-center justify-between">
      <h2 className="text-lg font-black">Filters</h2>
      <button onClick={close} className="text-text-muted">✕</button>
    </div>
    
    {content}
    
    {/* Sticky bottom: apply + count */}
    <div className="sticky bottom-0 mt-4 border-t border-border/30 bg-bg pt-4">
      <button className="w-full rounded-2xl bg-accent py-3.5 text-sm font-bold text-bg">
        Toon {resultCount} resultaten
      </button>
    </div>
  </div>
</div>
```

**Floating filter button (mobile):**

```tsx
// Sticky aan onderkant, altijd zichtbaar bij scrollen
<button className="fixed bottom-4 left-1/2 z-40 -translate-x-1/2 flex items-center gap-2 rounded-full bg-accent px-6 py-3 text-sm font-bold text-bg shadow-lg shadow-accent/20 lg:hidden">
  <FilterIcon className="h-4 w-4" />
  Filters
  {activeCount > 0 && (
    <span className="flex h-5 w-5 items-center justify-center rounded-full bg-bg text-[10px] font-black text-accent">
      {activeCount}
    </span>
  )}
</button>
```

---

## 2. 🧭 Navigatie Verbeteringen

### A. Breadcrumbs (uitbreiden)

Product page heeft al breadcrumbs ✅. Toevoegen op:

- `/sneakers` → `Home / Sneakers`
- `/merken/nike` → `Home / Merken / Nike`
- `/deals` → `Home / Deals`
- `/collecties/trending` → `Home / Collecties / Trending`

```astro
<nav class="mb-6 flex items-center gap-2 text-sm text-text-muted overflow-x-auto whitespace-nowrap" aria-label="Breadcrumb">
  <a href="/" class="hover:text-accent shrink-0">Home</a>
  <span class="text-border">/</span>
  <span class="text-text truncate">{currentPage}</span>
</nav>
```

### B. Search UX — Kritieke Bug Fix

**Probleem:** `SearchBar.tsx` importeert `sneakers` uit `'../data/mock'` — dit is de mock data, niet de database! Met 4100+ producten in DB werkt zoeken dus alleen op mock data.

**Fix:** Maak een API endpoint:

```ts
// /api/search.ts
export async function GET({ url }) {
  const q = url.searchParams.get('q') || '';
  if (q.length < 2) return new Response('[]');
  const results = await searchProducts(q, 6);
  return new Response(JSON.stringify(results));
}
```

SearchBar aanpassen naar fetch-based:
```tsx
const search = async (q: string) => {
  if (q.length < 2) return setResults([]);
  const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
  setResults(await res.json());
};

// Debounce 300ms
useEffect(() => {
  const timer = setTimeout(() => search(query), 300);
  return () => clearTimeout(timer);
}, [query]);
```

### C. Search in Header (desktop)

Voeg SearchBar toe aan header, naast navigatie-links:

```astro
<!-- Desktop: compact search in header -->
<div class="hidden md:block w-64">
  <SearchBar client:load />
</div>
```

### D. Mobile Header Search

Zoekicoon in header → tik → full-width search bar schuift open:

```tsx
// Mobile search overlay
<div className="fixed inset-x-0 top-0 z-50 bg-bg p-4 shadow-lg">
  <div className="flex items-center gap-3">
    <button onClick={close}><ArrowLeftIcon /></button>
    <SearchBar large autoFocus />
  </div>
</div>
```

### E. "Terug" UX

Op product pages, voeg een terug-knop toe boven de breadcrumbs:

```astro
<button onclick="history.back()" class="mb-4 flex items-center gap-1 text-sm text-text-muted hover:text-accent lg:hidden">
  <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
    <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
  </svg>
  Terug
</button>
```

---

## 3. 📱 Mobile-Specifieke Verbeteringen

### A. Product Cards — Horizontal Layout voor Lijsten

Roel vond de horizontale cards mooi. Gebruik ze op:
- Zoekresultaten
- Filter-pagina (optioneel, als lijst-view toggle)
- "Meer van [brand]" sectie

```astro
<!-- Horizontal card -->
<a href={url} class="flex gap-4 rounded-2xl border border-border/50 bg-bg-card p-3 transition-all hover:border-accent/30">
  <div class="h-24 w-24 shrink-0 overflow-hidden rounded-xl bg-bg-elevated">
    <img src={image} alt="" class="h-full w-full object-contain" />
  </div>
  <div class="flex flex-1 flex-col justify-between min-w-0">
    <div>
      <span class="text-[10px] font-bold uppercase tracking-widest text-text-muted">{brand}</span>
      <h3 class="truncate text-sm font-semibold text-text">{name}</h3>
      <p class="text-xs text-text-muted">{colorway}</p>
    </div>
    <div class="flex items-baseline gap-2">
      <span class="text-base font-black text-accent">€{price}</span>
      {oldPrice && <span class="text-xs text-text-muted line-through">€{oldPrice}</span>}
    </div>
  </div>
</a>
```

### B. Grid/List View Toggle

Op `/sneakers` pagina:

```tsx
<div className="flex gap-1 rounded-lg bg-bg-elevated p-1">
  <button className={`rounded-md p-1.5 ${view === 'grid' ? 'bg-bg-card text-text' : 'text-text-muted'}`}>
    <GridIcon className="h-4 w-4" />
  </button>
  <button className={`rounded-md p-1.5 ${view === 'list' ? 'bg-bg-card text-text' : 'text-text-muted'}`}>
    <ListIcon className="h-4 w-4" />
  </button>
</div>
```

### C. Sticky Sort Bar (mobile)

Sort bar moet sticky zijn zodat je altijd kunt sorteren:

```astro
<div class="sticky top-[65px] z-30 mb-4 ...">
```

### D. Pull-to-refresh feeling

Niet letterlijk pull-to-refresh, maar zorg dat de pagina responsive aanvoelt. Skeleton loading states bij filter-wijzigingen:

```tsx
// Skeleton card
<div className="animate-pulse rounded-2xl border border-border/50 bg-bg-card">
  <div className="aspect-square bg-bg-elevated" />
  <div className="space-y-2 p-4">
    <div className="h-3 w-16 rounded bg-bg-elevated" />
    <div className="h-4 w-full rounded bg-bg-elevated" />
    <div className="h-5 w-20 rounded bg-bg-elevated" />
  </div>
</div>
```

### E. Swipeable Product Images

Op de product page, als er meerdere afbeeldingen zijn, maak ze swipeable:

```tsx
<div className="flex snap-x snap-mandatory gap-4 overflow-x-auto scrollbar-hide">
  {images.map(img => (
    <div className="w-full shrink-0 snap-center">
      <img src={img} className="w-full object-contain" />
    </div>
  ))}
</div>
```

### F. Bottom Navigation Bar (mobile)

Overweeg een bottom nav bar voor de belangrijkste pagina's:

```astro
<nav class="fixed bottom-0 inset-x-0 z-40 border-t border-border/50 bg-bg/95 backdrop-blur-xl md:hidden">
  <div class="flex items-center justify-around py-2">
    <a href="/" class="flex flex-col items-center gap-0.5 px-3 py-1">
      <HomeIcon class="h-5 w-5" />
      <span class="text-[10px] font-bold">Home</span>
    </a>
    <a href="/sneakers" class="...">
      <SearchIcon />
      <span>Zoeken</span>
    </a>
    <a href="/deals" class="...">
      <TagIcon />
      <span>Deals</span>
    </a>
    <a href="/merken" class="...">
      <GridIcon />
      <span>Merken</span>
    </a>
  </div>
</nav>
<!-- Spacer -->
<div class="h-16 md:hidden" />
```

---

## 4. 🃏 Product Card Verbeteringen

### A. Shop count → meer informative

Huidige: `3 shops` → Beter: `Baskèts • Patta • +1`

```astro
<div class="text-[11px] text-text-muted">
  {shops.slice(0, 2).join(' • ')}
  {shops.length > 2 && ` • +${shops.length - 2}`}
</div>
```

### B. "Nieuw" badge

Naast de discount badge, toon ook een "Nieuw" badge voor recent toegevoegde sneakers (<7 dagen):

```astro
{isNew && !discount && (
  <div class="absolute left-3 top-3 z-10 rounded-full bg-accent px-2.5 py-1 text-xs font-bold text-bg">
    Nieuw
  </div>
)}
```

### C. Quick-view prijzen

Bij hover (desktop) of long-press (mobile), toon een mini price comparison:

```
Nike Air Max 90
Baskèts    €129,95
Patta      €134,95 
```

### D. Wishlist / Favoriet icoon

Hart-icoon rechtsboven in de card. Sla op in localStorage:

```astro
<button class="absolute right-3 top-3 z-10 rounded-full bg-bg/80 p-1.5 backdrop-blur-sm">
  <HeartIcon class="h-4 w-4 text-text-muted hover:text-deal" />
</button>
```

---

## 5. 📦 Product Page Verbeteringen

### A. Sticky "Koop" Bar (mobile)

Wanneer de prijsvergelijking uit beeld scrollt, toon een sticky bar onderaan:

```astro
<div class="fixed bottom-0 inset-x-0 z-40 border-t border-border/50 bg-bg/95 p-4 backdrop-blur-xl lg:hidden">
  <div class="flex items-center justify-between">
    <div>
      <div class="text-xs text-text-muted">Laagste prijs</div>
      <div class="text-lg font-black text-accent">€{lowestPrice}</div>
    </div>
    <a href={cheapestShopUrl} target="_blank" rel="nofollow"
       class="rounded-2xl bg-accent px-6 py-3 text-sm font-bold text-bg">
      Bekijk bij {cheapestShop} →
    </a>
  </div>
</div>
```

### B. Size Selector → Interactief

Maten zijn nu puur informatief. Maak ze klikbaar zodat je ziet welke shops die maat hebben:

```tsx
// Klik op maat → filter prijsvergelijking tabel op die maat
const [selectedSize, setSelectedSize] = useState<number | null>(null);

// In de prijstabel: filter offers die selectedSize bevatten
```

### C. Prijsgeschiedenis — Periode selector

Voeg knoppen toe: 1M / 3M / 6M / Alles

```tsx
<div className="flex gap-2 mb-4">
  {['1M', '3M', '6M', 'Alles'].map(period => (
    <button className={`rounded-lg px-3 py-1 text-xs font-bold ${
      active ? 'bg-accent text-bg' : 'bg-bg-elevated text-text-muted'
    }`}>{period}</button>
  ))}
</div>
```

### D. "Deel" knop

```astro
<button onclick="navigator.share?.({ title: '{name}', url: window.location.href })"
        class="rounded-xl bg-bg-elevated px-4 py-2 text-xs font-bold text-text-muted hover:text-text">
  Delen 📤
</button>
```

### E. Vergelijkbare sneakers (niet alleen zelfde merk)

Voeg naast "Meer van {brand}" ook toe: "Vergelijkbare modellen" — gebaseerd op dezelfde silhouette/categorie (bijv. alle Air Max 90 varianten).

---

## 6. 🏠 Homepage Verbeteringen

### A. Quick Category Links onder hero

Na de search bar, toon populaire categorieën:

```astro
<div class="mt-6 flex flex-wrap justify-center gap-2">
  {['Nike Air Max', 'New Balance 550', 'Adidas Samba', 'Nike Dunk', 'Jordan 1'].map(cat => (
    <a href={`/sneakers?q=${cat}`} 
       class="rounded-full border border-border/50 bg-bg-card px-4 py-2 text-xs font-bold text-text-secondary hover:border-accent/50 hover:text-accent">
      {cat}
    </a>
  ))}
</div>
```

### B. "Recent bekeken" sectie

localStorage-based, toon onder trending:

```tsx
// Sla slug op bij elk product page bezoek
const recentlyViewed = JSON.parse(localStorage.getItem('recentlyViewed') || '[]');
```

### C. USP Banner

Boven de footer, een vertrouwens-strip:

```astro
<section class="border-t border-border/30 bg-bg-card py-12">
  <div class="mx-auto max-w-7xl px-4 grid grid-cols-2 gap-8 sm:grid-cols-4">
    <div class="text-center">
      <div class="text-2xl">🔍</div>
      <h3 class="mt-2 text-sm font-bold">Vergelijk prijzen</h3>
      <p class="mt-1 text-xs text-text-muted">Bij 3+ Nederlandse shops</p>
    </div>
    <div class="text-center">
      <div class="text-2xl">📉</div>
      <h3 class="mt-2 text-sm font-bold">Prijshistorie</h3>
      <p class="mt-1 text-xs text-text-muted">Weet of je een goede deal hebt</p>
    </div>
    <div class="text-center">
      <div class="text-2xl">🔔</div>
      <h3 class="mt-2 text-sm font-bold">Prijsalerts</h3>
      <p class="mt-1 text-xs text-text-muted">Mis nooit meer een prijsdaling</p>
    </div>
    <div class="text-center">
      <div class="text-2xl">🇳🇱</div>
      <h3 class="mt-2 text-sm font-bold">100% NL shops</h3>
      <p class="mt-1 text-xs text-text-muted">Alleen betrouwbare shops</p>
    </div>
  </div>
</section>
```

### D. Pagination / Infinite Scroll

De homepage toont 8 trending + 6 deals — prima. Maar `/sneakers` toont 24 zonder pagination. Voeg toe:

```astro
<div class="mt-12 flex justify-center">
  <button class="rounded-2xl border border-border/50 bg-bg-card px-8 py-3 text-sm font-bold text-text-secondary hover:border-accent/50 hover:text-accent">
    Meer laden
  </button>
</div>
```

Of infinite scroll met Intersection Observer.

---

## 7. 📄 Ontbrekende Pagina's & Features

### A. Over SneakerPicks (`/over-ons`)
- Wie zijn we, hoe werkt het, welke shops
- Belangrijk voor vertrouwen + SEO

### B. FAQ (`/faq`)
- Hoe werkt de prijsvergelijking?
- Koop ik bij SneakerPicks?
- Hoe vaak worden prijzen bijgewerkt?
- Wat zijn de verzendkosten?

### C. Maattabel / Size Guide (`/maatgids`)
- Omrekentabel EU/US/UK per merk
- Nike valt klein, NB breed, etc.
- Link vanuit elke product page bij de maten-sectie

### D. Wishlist / Favorieten (`/favorieten`)
- localStorage-based (geen login nodig)
- Grid van opgeslagen sneakers
- Link in header (hartje met count badge)

### E. Prijsalerts — Werkend maken
- De knop bestaat maar doet niks
- Simpelste implementatie: email invullen → opslaan in DB → cron checkt prijsdalingen → stuur mail
- Of: Telegram/WhatsApp notificatie

### F. Blog / Nieuws (`/blog`)
- Sneaker release dates
- Best of sale roundups
- SEO content: "Beste hardloopschoenen 2026"
- Niet urgent, maar goed voor organisch verkeer

### G. Collectie pagina's verbeteren
- `/collecties/trending`, `/collecties/sale` bestaan al
- Voeg toe: `/collecties/under-100` (sneakers onder €100)
- `/collecties/nieuw` (afgelopen 7 dagen)

---

## 8. ⚡ Performance & Technische Verbeteringen

### A. Search fix (KRITIEK)
SearchBar importeert mock data. Moet naar API endpoint. Zie sectie 2B.

### B. Filter zonder page reload
Gebruik `fetch` + `pushState` in plaats van `window.location.href`. Gebruikers wachten nu op full reload bij elke filter-klik.

### C. Image optimization
- Gebruik `<picture>` met WebP + fallback
- Lazy loading is al aanwezig ✅
- Overweeg `srcset` voor responsive images

### D. Pagination API
`/sneakers` laadt nu max 24 producten server-side. Voeg cursor-based pagination toe.

---

## 9. 📊 Prioriteiten Matrix

| Prioriteit | Item | Impact | Effort |
|-----------|------|--------|--------|
| 🔴 P0 | Search fix (mock → DB) | Hoog | Laag |
| 🔴 P0 | Maat-filter toevoegen | Hoog | Medium |
| 🔴 P0 | Mobile filter bottom sheet | Hoog | Medium |
| 🟠 P1 | Filter zonder page reload | Hoog | Medium |
| 🟠 P1 | Collapsible filter sections | Medium | Laag |
| 🟠 P1 | Pagination op /sneakers | Hoog | Laag |
| 🟠 P1 | Search in header | Medium | Laag |
| 🟡 P2 | Sticky buy bar (product page) | Medium | Laag |
| 🟡 P2 | Sale toggle filter | Medium | Laag |
| 🟡 P2 | Kleur filter | Medium | Medium |
| 🟡 P2 | Quick category links (homepage) | Medium | Laag |
| 🟡 P2 | Maattabel pagina | Medium | Laag |
| 🟢 P3 | Bottom nav (mobile) | Medium | Medium |
| 🟢 P3 | Wishlist / favorieten | Medium | Medium |
| 🟢 P3 | Prijsalerts werkend | Hoog | Hoog |
| 🟢 P3 | Grid/list view toggle | Laag | Laag |
| 🟢 P3 | Over ons / FAQ pagina's | Laag | Laag |
| 🟢 P3 | Blog | Laag | Hoog |

---

## 10. 🎯 Samenvatting: Top 5 Quick Wins

1. **Fix search** — werkt nu op mock data, niet op echte DB (30 min fix)
2. **Maat-filter** — dé manier waarop sneakerheads zoeken ("heb ik maat 43?")
3. **Mobile filter bottom sheet** — huidige inline toggle is rommelig
4. **Pagination** — 24 producten zonder "meer laden" is te weinig
5. **Search in header** — zoeken moet altijd bereikbaar zijn, niet alleen op homepage

Deze 5 verbeteringen maken SneakerPicks drastisch bruikbaarder voor de gemiddelde Nederlandse sneakerhead die op z'n telefoon zit te browsen. 🔥
