Update Home Page
|
After Width: | Height: | Size: 6.0 MiB |
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 5.8 MiB |
|
After Width: | Height: | Size: 368 KiB |
|
After Width: | Height: | Size: 6.3 MiB |
|
After Width: | Height: | Size: 21 MiB |
|
After Width: | Height: | Size: 12 MiB |
|
After Width: | Height: | Size: 806 KiB |
|
After Width: | Height: | Size: 15 MiB |
|
After Width: | Height: | Size: 26 MiB |
|
After Width: | Height: | Size: 9.6 MiB |
|
After Width: | Height: | Size: 6.8 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,40 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
|
||||||
|
|
||||||
|
interface CoverSlideshowProps {
|
||||||
|
images: string[];
|
||||||
|
intervalMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Foto latar kartu ekosistem — kalau lebih dari satu, bergantian dengan crossfade + blur. */
|
||||||
|
export default function CoverSlideshow({ images, intervalMs = 4200 }: CoverSlideshowProps) {
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
const prefersReduced = useReducedMotion();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (images.length <= 1) return;
|
||||||
|
const t = setInterval(() => setIndex(i => (i + 1) % images.length), intervalMs);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, [images.length, intervalMs]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'absolute', inset: 0, overflow: 'hidden' }}>
|
||||||
|
<AnimatePresence>
|
||||||
|
<motion.img
|
||||||
|
key={index}
|
||||||
|
src={images[index]}
|
||||||
|
alt=""
|
||||||
|
initial={prefersReduced ? { opacity: 0 } : { opacity: 0, filter: 'blur(18px)', scale: 1.1 }}
|
||||||
|
animate={{ opacity: 1, filter: 'blur(0px)', scale: 1 }}
|
||||||
|
exit={prefersReduced ? { opacity: 0 } : { opacity: 0, filter: 'blur(18px)', scale: 1.05 }}
|
||||||
|
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
|
||||||
|
style={{
|
||||||
|
position: 'absolute', inset: 0,
|
||||||
|
width: '100%', height: '100%',
|
||||||
|
objectFit: 'cover',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ export interface Module {
|
|||||||
contact?: string[];
|
contact?: string[];
|
||||||
/** Logo resmi modul (jika sudah tersedia) — dipakai di kartu ekosistem menggantikan ikon custom. */
|
/** Logo resmi modul (jika sudah tersedia) — dipakai di kartu ekosistem menggantikan ikon custom. */
|
||||||
logoImage?: string;
|
logoImage?: string;
|
||||||
|
/** Foto dokumentasi modul (jika sudah tersedia) — mengisi latar kartu ekosistem, bergantian dengan efek blur bila lebih dari satu. */
|
||||||
|
coverImages?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules: Module[] = [
|
const modules: Module[] = [
|
||||||
@@ -38,6 +40,11 @@ const modules: Module[] = [
|
|||||||
],
|
],
|
||||||
contact: ['IG @bhumi.anindha', '+123-456-7890 (kontak sedang diperbarui)'],
|
contact: ['IG @bhumi.anindha', '+123-456-7890 (kontak sedang diperbarui)'],
|
||||||
logoImage: '/assets/logo/ekosistem/bhumi-anindha/mark.png',
|
logoImage: '/assets/logo/ekosistem/bhumi-anindha/mark.png',
|
||||||
|
coverImages: [
|
||||||
|
'/assets/images/ekosistem/bhumi-anindha/01-BhumiAnindha.jpg',
|
||||||
|
'/assets/images/ekosistem/bhumi-anindha/02-BhumiAnindha.jpg',
|
||||||
|
'/assets/images/ekosistem/bhumi-anindha/03-BhumiAnindha.jpg',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'paon',
|
id: 'paon',
|
||||||
@@ -60,6 +67,7 @@ const modules: Module[] = [
|
|||||||
{ icon: '🍷', title: 'Cerita Rasa', desc: 'Thematic dinner 5–7 course, maksimal 20 tamu, sebulan sekali.' },
|
{ icon: '🍷', title: 'Cerita Rasa', desc: 'Thematic dinner 5–7 course, maksimal 20 tamu, sebulan sekali.' },
|
||||||
],
|
],
|
||||||
logoImage: '/assets/logo/ekosistem/paon/mark.png',
|
logoImage: '/assets/logo/ekosistem/paon/mark.png',
|
||||||
|
coverImages: ['/assets/images/ekosistem/paon/01-Paon.JPG'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'kaind',
|
id: 'kaind',
|
||||||
@@ -81,6 +89,7 @@ const modules: Module[] = [
|
|||||||
{ icon: '✂️', title: 'Made to Order', desc: 'Pesan pakaian yang dibuat khusus sesuai ukuran dan pilihan kain Anda.' },
|
{ icon: '✂️', title: 'Made to Order', desc: 'Pesan pakaian yang dibuat khusus sesuai ukuran dan pilihan kain Anda.' },
|
||||||
{ icon: '🎨', title: 'Warna dari Bumi', desc: 'Pewarna alami dari indigo, mengkudu, kulit kayu, dan rempah lokal.' },
|
{ icon: '🎨', title: 'Warna dari Bumi', desc: 'Pewarna alami dari indigo, mengkudu, kulit kayu, dan rempah lokal.' },
|
||||||
],
|
],
|
||||||
|
coverImages: ['/assets/images/ekosistem/kaind/01-Kaind.jpg'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'citta-rahayu',
|
id: 'citta-rahayu',
|
||||||
@@ -101,6 +110,7 @@ const modules: Module[] = [
|
|||||||
{ icon: '👥', title: 'Kumpul', desc: 'Pertemuan komunitas untuk berbagi dan tumbuh bersama.' },
|
{ icon: '👥', title: 'Kumpul', desc: 'Pertemuan komunitas untuk berbagi dan tumbuh bersama.' },
|
||||||
{ icon: '🏔️', title: 'Pulang', desc: 'Retreat mendalam untuk pemulihan dan penyatuan diri yang utuh.' },
|
{ icon: '🏔️', title: 'Pulang', desc: 'Retreat mendalam untuk pemulihan dan penyatuan diri yang utuh.' },
|
||||||
],
|
],
|
||||||
|
coverImages: ['/assets/images/ekosistem/citta-rahayu/01-CittaRahayu.JPG'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'pasar-bahagia',
|
id: 'pasar-bahagia',
|
||||||
@@ -125,6 +135,11 @@ const modules: Module[] = [
|
|||||||
],
|
],
|
||||||
contact: ['+62 815 7330 5808 (Welin Cakra)', '@anindhaloka_id', '@kreasi.nin'],
|
contact: ['+62 815 7330 5808 (Welin Cakra)', '@anindhaloka_id', '@kreasi.nin'],
|
||||||
logoImage: '/assets/logo/ekosistem/pasar-bahagia/mark.png',
|
logoImage: '/assets/logo/ekosistem/pasar-bahagia/mark.png',
|
||||||
|
coverImages: [
|
||||||
|
'/assets/images/ekosistem/pasar-bahagia/01-PasarBahagia.JPG',
|
||||||
|
'/assets/images/ekosistem/pasar-bahagia/02-PasarBahagia.JPG',
|
||||||
|
'/assets/images/ekosistem/pasar-bahagia/03-PasarBahagia.jpg',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'learnnin',
|
id: 'learnnin',
|
||||||
@@ -142,6 +157,10 @@ const modules: Module[] = [
|
|||||||
{ icon: '🌱', title: 'Ruang yang Sedang Tumbuh', desc: 'Daftar program LearnNIN sedang kami tumbuhkan bersama komunitas. Hubungi kami untuk info terbaru atau mengusulkan kolaborasi program.' },
|
{ icon: '🌱', title: 'Ruang yang Sedang Tumbuh', desc: 'Daftar program LearnNIN sedang kami tumbuhkan bersama komunitas. Hubungi kami untuk info terbaru atau mengusulkan kolaborasi program.' },
|
||||||
],
|
],
|
||||||
logoImage: '/assets/logo/ekosistem/learnin/mark.png',
|
logoImage: '/assets/logo/ekosistem/learnin/mark.png',
|
||||||
|
coverImages: [
|
||||||
|
'/assets/images/ekosistem/learnin/01-Learnnin.JPG',
|
||||||
|
'/assets/images/ekosistem/learnin/02-Learnnin.jpg',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'lokanin',
|
id: 'lokanin',
|
||||||
@@ -162,6 +181,11 @@ const modules: Module[] = [
|
|||||||
{ icon: '📸', title: 'Ruang yang Sedang Tumbuh', desc: 'Nama ruang, foto, dan contoh kegiatan sedang kami lengkapi bersama komunitas. Hubungi kami untuk info dan ketersediaan terkini.' },
|
{ icon: '📸', title: 'Ruang yang Sedang Tumbuh', desc: 'Nama ruang, foto, dan contoh kegiatan sedang kami lengkapi bersama komunitas. Hubungi kami untuk info dan ketersediaan terkini.' },
|
||||||
],
|
],
|
||||||
contact: ['+62 815 7330 5808 (Welin Cakra)', '@anindhaloka_id', '@kreasi.nin'],
|
contact: ['+62 815 7330 5808 (Welin Cakra)', '@anindhaloka_id', '@kreasi.nin'],
|
||||||
|
coverImages: [
|
||||||
|
'/assets/images/ekosistem/lokanin/01-Lokanin.JPG',
|
||||||
|
'/assets/images/ekosistem/lokanin/02-Lokanin.JPG',
|
||||||
|
'/assets/images/ekosistem/lokanin/03-Lokanin.JPG',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'temannin',
|
id: 'temannin',
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ h4 { font-size: 1.15rem; font-weight: 600; }
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
|
grid-auto-flow: row dense;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
.ecosystem-mosaic__item--featured { grid-column: span 2; }
|
.ecosystem-mosaic__item--featured { grid-column: span 2; }
|
||||||
@@ -442,21 +443,17 @@ h4 { font-size: 1.15rem; font-weight: 600; }
|
|||||||
}
|
}
|
||||||
.card:hover .card__cover-emoji { transform: scale(1.08); }
|
.card:hover .card__cover-emoji { transform: scale(1.08); }
|
||||||
|
|
||||||
/* Badge putih untuk logo resmi modul (kontras terjaga di atas cover gelap apa pun warnanya) */
|
/* Logo resmi modul, kecil di pojok — tanpa frame putih, drop-shadow saja untuk kontras */
|
||||||
.card__cover-logo-badge {
|
.card__cover-logo-badge {
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
top: 14px; left: 16px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
background: #fff;
|
|
||||||
border-radius: var(--r-lg);
|
|
||||||
padding: 12px 18px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 8px 24px rgba(0,0,0,.22);
|
|
||||||
max-width: 76%;
|
|
||||||
max-height: 68%;
|
|
||||||
transition: transform .3s var(--ease);
|
transition: transform .3s var(--ease);
|
||||||
}
|
}
|
||||||
|
.card__cover-logo-badge img { display: block; max-width: 100%; object-fit: contain; filter: drop-shadow(0 2px 6px rgba(0,0,0,.45)); }
|
||||||
.card__cover-logo-badge img { display: block; max-width: 100%; object-fit: contain; }
|
.card__cover-logo-badge img { display: block; max-width: 100%; object-fit: contain; }
|
||||||
.card:hover .card__cover-logo-badge { transform: scale(1.06); }
|
.card:hover .card__cover-logo-badge { transform: scale(1.06); }
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import ArcDivider from '../components/ArcDivider';
|
|||||||
import Reveal from '../components/Reveal';
|
import Reveal from '../components/Reveal';
|
||||||
import TiltCard from '../components/TiltCard';
|
import TiltCard from '../components/TiltCard';
|
||||||
import CountUp from '../components/CountUp';
|
import CountUp from '../components/CountUp';
|
||||||
|
import CoverSlideshow from '../components/CoverSlideshow';
|
||||||
|
|
||||||
const EASE_ORGANIC = [0.22, 1, 0.36, 1] as const;
|
const EASE_ORGANIC = [0.22, 1, 0.36, 1] as const;
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ const jalurList = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const FEATURED_IDX = [0, 4];
|
const FEATURED_IDX = [0, 1, 2, 4];
|
||||||
|
|
||||||
const heroContainer = {
|
const heroContainer = {
|
||||||
hidden: {},
|
hidden: {},
|
||||||
@@ -267,15 +268,27 @@ export default function Home() {
|
|||||||
{/* ── Cover ── */}
|
{/* ── Cover ── */}
|
||||||
<div
|
<div
|
||||||
className={`card__cover${featured ? ' card__cover--tall' : ''}`}
|
className={`card__cover${featured ? ' card__cover--tall' : ''}`}
|
||||||
style={{ background: `linear-gradient(140deg, ${m.gradientFrom} 0%, ${m.gradientTo} 55%, ${m.color} 100%)` }}
|
style={{ background: m.coverImages ? m.gradientFrom : `linear-gradient(140deg, ${m.gradientFrom} 0%, ${m.gradientTo} 55%, ${m.color} 100%)` }}
|
||||||
>
|
>
|
||||||
|
{m.coverImages && <CoverSlideshow images={m.coverImages} />}
|
||||||
|
|
||||||
|
{!m.coverImages && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute', inset: 0,
|
||||||
|
background: `linear-gradient(140deg, ${m.gradientFrom} 0%, ${m.gradientTo} 55%, ${m.color} 100%)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div className="card__cover-blob" style={{ width: 140, height: 140, top: -40, right: -30, background: m.color, opacity: .28 }} />
|
<div className="card__cover-blob" style={{ width: 140, height: 140, top: -40, right: -30, background: m.color, opacity: .28 }} />
|
||||||
<div className="card__cover-blob" style={{ width: 90, height: 90, bottom: -20, left: 16, background: 'rgba(255,255,255,.08)' }} />
|
<div className="card__cover-blob" style={{ width: 90, height: 90, bottom: -20, left: 16, background: 'rgba(255,255,255,.08)' }} />
|
||||||
<div className="card__cover-blob" style={{ width: 60, height: 60, top: 20, left: 20, background: 'rgba(255,255,255,.06)' }} />
|
<div className="card__cover-blob" style={{ width: 60, height: 60, top: 20, left: 20, background: 'rgba(255,255,255,.06)' }} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{m.logoImage ? (
|
{m.logoImage ? (
|
||||||
<div className="card__cover-logo-badge">
|
<div className="card__cover-logo-badge">
|
||||||
<img src={m.logoImage} alt={m.name} style={{ height: featured ? 68 : 50 }} />
|
<img src={m.logoImage} alt={m.name} style={{ height: featured ? 36 : 28 }} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="card__cover-emoji">
|
<span className="card__cover-emoji">
|
||||||
|
|||||||