# 🏠 Dokumentasi Halaman Beranda (Donasi)
**File:** [src/pages/Home.tsx](file:///d:/Build/Personal%20Branding/Project/Klien/4.%20Hayfala/Kejayaan/src/pages/Home.tsx) | **Route:** `/`
**Stack:** React 18 · TypeScript · TailwindCSS v3 · Supabase · Vite

---

## 1. Ringkasan

Halaman utama aplikasi donasi. Konten berurutan dari atas ke bawah:
1. Banner Carousel
2. Pilih Akad (Sedekah, Wakaf, Zakat, Qurban)
3. Program Unggulan (horizontal scroll)
4. Menu Program (8 kategori)
5. Program Terbaru (horizontal scroll)
6. Program Khusus — Infinity Carousel
7. Doa Orang Baik
8. Tentang Kami

---

## 2. Design System

### Warna Brand
```js
// tailwind.config.js
brand: {
  DEFAULT: "#164B84",      // Biru tua (utama)
  dark:    "#0e3460",      // Hover
  light:   "#FFF7F7",      // Background halaman
  accent:  "#9EEFE5",      // Tosca (aksen)
  50:  "#e8f0fa",          // Background chip/btn pastel
  100: "#c5d9f0",          // Hover bg
}
```

### Progress Bar Gradient
```css
[data-slot="progress-indicator"] {
  background: linear-gradient(90deg, #164B84 0%, #9EEFE5 100%) !important;
}
```

### CSS Utilities Penting
```css
.hide-scrollbar { -ms-overflow-style:none; scrollbar-width:none; }
.hide-scrollbar::-webkit-scrollbar { display:none; }
.line-clamp-2 { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.line-clamp-3 { display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }
```

---

## 3. Layout Global Shell

```tsx
// App.tsx
<div className="h-screen bg-brand-light overflow-hidden">
  <div className="bg-white h-full shadow-lg relative flex flex-col overflow-hidden">
    <Header />
    <main className="flex-1 overflow-y-auto app-scroll-container pt-[104px] md:pt-[68px] pb-24">
      <Home />
    </main>
    <BottomNav />
  </div>
</div>
```

> **Kunci:** Scroll terjadi di `.app-scroll-container`, bukan `window`. Header dan BottomNav memantau container ini.

```tsx
// Container Home
<div className="pb-4 max-w-6xl mx-auto">
  {/* semua seksi */}
</div>
```

---

## 4. Komponen Header

```
fixed top-0 left-0 right-0 w-full z-50
bg-white/80 backdrop-blur-xl border-b border-gray-100/60
shadow-[0_4px_20px_rgba(0,0,0,0.06)]
transition-all duration-300
```

**Auto-hide saat scroll:**
```ts
if (currentScrollY > lastScrollY.current && currentScrollY > 60) setVisible(false);
else setVisible(true);
// class: visible → translate-y-0 | hidden → -translate-y-full
```

**Logo:**
```tsx
<div className="w-8 h-8 bg-brand rounded-lg flex items-center justify-center">
  {/* SVG hati putih */}
</div>
<span className="text-lg font-bold text-brand">PILAR DAKWAH</span>
```

**Search Bar:**
```tsx
<input className="w-full pl-10 pr-10 py-2.5 bg-gray-100/80 rounded-xl text-sm
  border border-transparent focus:border-brand/30 focus:bg-white focus:ring-2 focus:ring-brand/10
  transition-all duration-200 outline-none" />
```
- Sinkronisasi ke URL `?q=` via `useSearchParams`
- Tombol `X` untuk clear

---

## 5. BottomNav — Floating Pill

```tsx
<nav className="fixed bottom-4 left-1/2 -translate-x-1/2 w-[calc(100%-2rem)] max-w-2xl z-50
  bg-white/75 backdrop-blur-2xl border border-white/40 rounded-3xl
  shadow-[0_8px_32px_rgba(0,0,0,0.12),0_2px_8px_rgba(0,0,0,0.06)]
  transition-all duration-300">
```

| Route | Label | Icon Lucide |
|-------|-------|------------|
| `/` | Home | [Home](file:///d:/Build/Personal%20Branding/Project/Klien/4.%20Hayfala/Kejayaan/src/pages/Home.tsx#325-579) |
| `/produk` | Produk | [List](file:///d:/Build/Personal%20Branding/Project/Klien/4.%20Hayfala/Kejayaan/src/pages/ProgramList.tsx#132-358) |
| `/kolaborasi` | Kolaborasi | `Handshake` |
| `/galeri` | Galeri | `Images` |
| `/profil` | Profil | `User` |

**State aktif:** `text-brand scale-105 fill-current strokeWidth={2.5} font-semibold`
**Inaktif:** `text-gray-400 strokeWidth={1.8} font-medium`

---

## 6. Mode Search (`?q=keyword`)

```tsx
const filteredPrograms = programs.filter(p =>
  p.title.toLowerCase().includes(searchQuery.toLowerCase())
);
```

**Grid hasil:** `grid grid-cols-1 md:grid-cols-2 gap-3`

**Card horizontal:**
```tsx
<div className="bg-white rounded-xl border border-gray-100 overflow-hidden shadow-sm
  cursor-pointer hover:shadow-md transition-shadow flex group">
  <div className="w-28 md:w-36 flex-shrink-0 relative">
    <img className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" />
    {urgent && <span className="absolute top-2 left-2 px-2 py-0.5 bg-red-500 text-white text-[10px] font-bold rounded-full">Mendesak</span>}
  </div>
  <div className="flex-1 p-3 flex flex-col justify-between">
    <div className="flex items-center gap-2 mb-1">
      <span className="text-[10px] px-2 py-0.5 bg-brand-50 text-brand font-medium rounded-full">{akad}</span>
      <span className="text-[10px] text-gray-400">{category}</span>
    </div>
    <h4 className="text-sm font-semibold text-gray-800 line-clamp-2 mb-2">{title}</h4>
    <p className="text-sm font-bold text-brand mb-1">{formatCurrency(collected)}</p>
    <div className="flex items-center gap-2">
      <Progress value={pct} className="flex-1 h-1.5" />
      <span className="text-[10px] text-gray-500">{pct}%</span>
    </div>
  </div>
</div>
```

---

## 7. Seksi A — Banner Carousel

```
px-4 md:px-8 mb-6 | h-48 | auto-advance 5 detik
overflow-hidden rounded-2xl
```

**Track:** `flex transition-transform duration-500 ease-out` + `style={{ transform: translateX(-${current * 100}%) }}`

**Struktur slide:**
```tsx
<div className="min-w-full relative h-48">
  <img className="w-full h-full object-cover" />
  <div className={`absolute inset-0 bg-gradient-to-r ${banner.color} opacity-90`} />
  <div className="absolute inset-0 p-6 flex flex-col justify-center text-white">
    <h2 className="text-2xl font-bold mb-1">{title}</h2>
    <p className="text-sm font-medium mb-1">{subtitle}</p>
    <p className="text-xs opacity-90 mb-4 line-clamp-2">{description}</p>
    <button className="self-start px-4 py-2 bg-white/20 backdrop-blur-sm rounded-lg text-sm font-medium hover:bg-white/30">
      {button_text}
    </button>
  </div>
</div>
```

**Dots:** aktif `bg-white`, inaktif `bg-white/50` — `absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-2`

**Supabase tabel `banners`:** `title, subtitle, description, button_text, image, color (Tailwind gradient class), sort_order, is_active`

---

## 8. Seksi B — Pilih Akad

```
px-4 md:px-8 mb-6 | Layout: flex justify-between gap-2
```

| id | Nama | Icon |
|----|------|------|
| `sedekah` | Sedekah | `HandCoins` (Lucide) |
| `wakaf` | Wakaf | `Landmark` (Lucide) |
| `zakat` | Zakat | `Scale` (Lucide) |
| `qurban` | Qurban | `QurbanIcon` (custom SVG) |

```tsx
<button onClick={() => navigate(`/donasi?akad=${id}`)}
  className="flex flex-col items-center gap-2 p-3 rounded-xl bg-brand-50 hover:bg-brand-100 transition-colors flex-1">
  <div className="w-10 h-10 rounded-full bg-brand-100 flex items-center justify-center">
    <Icon className="w-5 h-5 text-brand" />
  </div>
  <span className="text-xs font-medium text-gray-700">{name}</span>
</button>
```

---

## 9. Seksi C — Program Unggulan

```
px-4 md:px-8 mb-6 | Data: programs.slice(0, 3)
Scroll: flex gap-4 overflow-x-auto pb-2 -mx-4 px-4 hide-scrollbar
```

**Header seksi (pattern berulang):**
```tsx
<div className="flex items-center justify-between mb-4">
  <h3 className="text-base font-semibold text-gray-800">{judul}</h3>
  <button onClick={() => navigate("/donasi")}
    className="text-sm text-brand font-medium flex items-center gap-1 hover:text-brand-dark">
    Lihat Semua <ChevronRight className="w-4 h-4" />
  </button>
</div>
```

**Komponen ProgramCard:**
```tsx
<div onClick={onClick}
  className="min-w-[260px] bg-white rounded-xl border border-gray-100 overflow-hidden shadow-sm
    cursor-pointer hover:shadow-md transition-shadow flex-shrink-0">
  <div className="relative h-32">
    <img className="w-full h-full object-cover" />
    {urgent && <span className="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white text-xs font-medium rounded-full">Mendesak</span>}
  </div>
  <div className="p-3">
    <h4 className="text-sm font-semibold text-gray-800 line-clamp-2 mb-2 min-h-[2.5rem]">{title}</h4>
    <p className="text-sm font-bold text-brand mb-2">{formatCurrency(collected)}</p>
    <div className="flex items-center gap-2">
      <Progress value={(collected / target) * 100} className="flex-1 h-2" />
      <span className="text-xs text-gray-500">{pct}%</span>
    </div>
    <p className="text-xs text-gray-500 mt-1">Terkumpul</p>
  </div>
</div>
```

---

## 10. Seksi D — Menu Program

```
px-4 md:px-8 mb-6 | grid grid-cols-4 md:grid-cols-8 gap-4
```

| id | Nama | Icon |
|----|------|------|
| `pendidikan` | Pendidikan | `GraduationCapIcon` |
| `kesehatan` | Kesehatan | `HeartPulseIcon` |
| `sosial` | Sosial | `UsersIcon` |
| `kemanusiaan` | Kemanusiaan | `HandHeartIcon` |
| `masjid` | Masjid | `MosqueIcon` |
| `dakwah` | Dakwah | `MicIcon` |
| `bencana` | Bencana | `HomeIcon` |
| `lainnya` | Lainnya | `MoreHorizontal` |

```tsx
<button onClick={() => navigate(`/donasi?category=${id}`)}
  className="flex flex-col items-center gap-2">
  <div className="w-12 h-12 rounded-xl bg-gray-100 flex items-center justify-center hover:bg-brand-50 transition-colors">
    <Icon className="w-6 h-6 text-gray-600" />
  </div>
  <span className="text-xs text-gray-600 text-center">{name}</span>
</button>
```

> Icon kustom `QurbanIcon`, `GraduationCapIcon`, dll berada di [src/components/icons.tsx](file:///d:/Build/Personal%20Branding/Project/Klien/4.%20Hayfala/Kejayaan/src/components/icons.tsx) sebagai SVG React component.

---

## 11. Seksi E — Program Terbaru

Identik dengan Unggulan, tapi data tanpa `.slice()`:
```tsx
const latestPrograms = programs; // semua program
```
Gunakan komponen `<ProgramCard>` dan layout horizontal scroll yang sama.

---

## 12. Seksi F — Infinity Loop Carousel

**Komponen:** [InfinityCarousel](file:///d:/Build/Personal%20Branding/Project/Klien/4.%20Hayfala/Kejayaan/src/pages/Home.tsx#37-141) | **Data:** tabel `highlights`

**Teknik infinite loop:**
```ts
// Clone item terakhir di index 0, item pertama di index akhir
const extendedItems = [items[items.length - 1], ...items, items[0]];
const realIndex = currentIndex + 1; // +1 karena clone di depan

// Auto-advance
setTimeout(() => setCurrentIndex(prev => prev + 1), 4000);

// Saat melewati batas → reset tanpa animasi
if (currentIndex >= items.length) {
  setTimeout(() => { setIsTransitioning(false); setCurrentIndex(0); }, 500);
}
```

**JSX:**
```tsx
<div className="relative overflow-hidden rounded-2xl">
  <div className={`flex ${isTransitioning ? "transition-transform duration-500 ease-out" : ""}`}
    style={{ transform: `translateX(-${realIndex * 100}%)` }}>
    {extendedItems.map((item, idx) => (
      <div key={`${item.id}-${idx}`}
        className={`min-w-full relative bg-gradient-to-r ${item.bg_color} rounded-2xl overflow-hidden`}>
        <div className="flex items-stretch h-[180px] md:h-[200px]">
          {/* Kiri: teks */}
          <div className="flex-1 p-5 flex flex-col justify-center">
            <span className="inline-block self-start px-3 py-1 bg-brand/10 text-brand text-xs font-bold rounded-full mb-3">{item.tag}</span>
            <h3 className="text-xl font-extrabold text-gray-900 mb-1 leading-tight">{item.title}</h3>
            <p className="text-sm text-gray-600 mb-4 line-clamp-2">{item.subtitle}</p>
            <button className="self-start px-5 py-2.5 bg-brand text-white rounded-full text-sm font-semibold hover:bg-brand-dark transition-colors shadow-md shadow-brand/20">
              {item.button_text}
            </button>
          </div>
          {/* Kanan: gambar (45%) */}
          <div className="w-[45%] relative flex-shrink-0">
            <div className="absolute inset-0 bg-gradient-to-l from-transparent to-white/30 z-10 rounded-r-2xl" />
            <img src={item.image} className="w-full h-full object-cover rounded-r-2xl" />
          </div>
        </div>
      </div>
    ))}
  </div>
  {/* Dots */}
  <div className="absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-2">
    {items.map((_, i) => (
      <button key={i} onClick={() => { setIsTransitioning(true); setCurrentIndex(i); }}
        className={`h-2 rounded-full transition-all duration-300
          ${activeIndex === i ? "bg-brand w-6" : "bg-gray-300 w-2"}`} />
    ))}
  </div>
</div>
```

**Supabase tabel `highlights`:** `tag, title, subtitle, button_text, image, bg_color, sort_order, is_active`

---

## 13. Seksi G — Doa Orang Baik

**Data:** tabel `prayers` | **Scroll manual** dengan ref

**Background seksi:**
```tsx
<div className="relative bg-gradient-to-b from-brand-accent/20 via-brand-accent/10 to-transparent py-5">
```

**Tombol panah:**
```tsx
<button onClick={() => scrollRef.current?.scrollBy({ left: -240, behavior: "smooth" })}
  className="absolute left-1 top-1/2 -translate-y-1/2 z-10 w-8 h-8
    bg-white/80 backdrop-blur rounded-full flex items-center justify-center shadow-md hover:bg-white">
  <ChevronLeft className="w-4 h-4 text-gray-600" />
</button>
```

**Card Doa (min-w-[220px]):**
```tsx
<div className="min-w-[220px] max-w-[220px] bg-white rounded-2xl p-4 shadow-sm flex-shrink-0 flex flex-col">
  {/* Header: avatar + nama + tanggal + program */}
  <div className="flex items-start gap-2 mb-2">
    <div className="w-9 h-9 bg-gray-200 rounded-full flex items-center justify-center flex-shrink-0">
      {/* SVG silhouette */}
    </div>
    <div className="flex-1 min-w-0">
      <div className="flex items-center gap-1">
        <p className="text-xs font-bold text-gray-800 truncate">{name}</p>
        <span className="text-gray-300 text-[10px]">•</span>
        <span className="text-[10px] text-amber-500">{timeAgo}</span>
      </div>
      <p className="text-[10px] text-brand truncate">{program}</p>
    </div>
    <button><MoreHorizontal className="w-4 h-4 text-gray-400" /></button>
  </div>
  {/* Teks doa */}
  <div className="flex-1 mb-3">
    <p className="text-sm text-gray-700 leading-relaxed line-clamp-3">
      {message} <span className="text-brand text-xs font-medium cursor-pointer hover:underline">Lihat selengkapnya</span>
    </p>
  </div>
  {/* Counter */}
  <p className="text-[10px] text-gray-400 mb-3">{displayAmiin} orang mengaminkan doa ini</p>
  {/* Tombol Aamiin */}
  <button onClick={() => toggleLike(prayer.id)}
    className="flex items-center gap-2 pt-2 border-t border-gray-100 group">
    <Heart className={`w-4 h-4 transition-all duration-300
      ${isLiked ? "text-red-500 fill-red-500 scale-110" : "text-gray-400 group-hover:text-red-400"}`} />
    <span className={`text-sm font-medium ${isLiked ? "text-red-500" : "text-gray-500 group-hover:text-red-400"}`}>Aamiin</span>
  </button>
</div>
```

**Like dengan localStorage:**
```ts
const [likedPrayers, setLikedPrayers] = useState<string[]>(() => {
  try { return JSON.parse(localStorage.getItem("likedPrayers") || "[]"); }
  catch { return []; }
});
const toggleLike = (id: string) => {
  setLikedPrayers(prev => {
    const next = prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id];
    localStorage.setItem("likedPrayers", JSON.stringify(next));
    return next;
  });
};
// Count optimistic lokal (tidak update DB)
const displayAmiin = isLiked ? (prayer.amiin_count || 0) + 1 : (prayer.amiin_count || 0);
```

**Supabase tabel `prayers`:** `donation_id (nullable), name, program, message, amiin_count, created_at`

---

## 14. Seksi H — Tentang Kami

```tsx
<div className="bg-white rounded-xl border border-gray-100 overflow-hidden divide-y divide-gray-100">
  {aboutItemsList.map((item) => (
    <div onClick={() => navigate(`/tentang/${item.id}`)}
      className="flex items-center gap-4 p-4 cursor-pointer hover:bg-gray-50 transition-colors">
      <div className="flex-1 min-w-0">
        <h4 className="text-sm font-bold text-gray-800 mb-1">{item.title}</h4>
        <p className="text-xs text-gray-500 mb-2 line-clamp-2">{item.description}</p>
        <span className="text-xs text-brand font-medium flex items-center gap-1">
          {item.link_text || 'Baca selengkapnya'} <ChevronRight className="w-3 h-3" />
        </span>
      </div>
      <img src={item.image} className="w-20 h-20 rounded-xl object-cover flex-shrink-0" />
    </div>
  ))}
</div>
<p className="text-center text-[10px] text-gray-400 mt-3 px-2 leading-relaxed">
  Berdiri sejak 2015, Pilar Dakwah memiliki izin Pengumpulan Uang dan Barang dari Kemensos...
</p>
```

**Supabase tabel `about_items`:** `title, description, link_text, image, content, content_images, sort_order, is_active`

---

## 15. Hooks & Data

```ts
useBanners()    → { banners: Banner[] }        // tabel: banners
useHighlights() → { highlights: Highlight[] }  // tabel: highlights
usePrayers()    → { prayers: Prayer[] }        // tabel: prayers
usePrograms()   → { programs: Program[] }      // tabel: programs
useAboutItems() → { items: AboutItem[] }       // tabel: about_items
```

Semua hook: cek `VITE_SUPABASE_URL` → fallback ke static data jika belum dikonfigurasi.

---

## 16. Checklist Replikasi

- [ ] Brand colors di [tailwind.config.js](file:///d:/Build/Personal%20Branding/Project/Klien/4.%20Hayfala/Kejayaan/tailwind.config.js)
- [ ] CSS utilities (hide-scrollbar, line-clamp, progress gradient) di [index.css](file:///d:/Build/Personal%20Branding/Project/Klien/4.%20Hayfala/Kejayaan/src/index.css)
- [ ] Layout shell `h-screen bg-brand-light + app-scroll-container`
- [ ] Header (glassmorphism + auto-hide + search URL-sync)
- [ ] BottomNav (floating pill + 5 tab)
- [ ] Banner Carousel (5 detik + dots)
- [ ] Pilih Akad (4 tombol flex-1 + icon kustom)
- [ ] ProgramCard (`min-w-[260px]` + progress gradient + badge)
- [ ] Menu Program (4×2 grid)
- [ ] InfinityCarousel (clone trick + dots + 4 detik)
- [ ] DoaOrangBaik (panah + localStorage like + optimistic count)
- [ ] Tentang Kami (divide-y list)
- [ ] Tabel Supabase: `banners, highlights, prayers, programs, about_items`
