// Homepage-only section compositions for The Vivaan Hotel & Resorts. // Depends on shared/vivaan-sections.jsx being loaded first. const { Button } = window.VivaanDesignSystem_b1c8ba; const { PhotoBlock, PhotoSlideshow, HoverImageSwap, SectionLabel, EditorialHeading, BodyCopy } = window; /* ---------------------------------------------------------------------- */ /* 2. Welcome */ /* ---------------------------------------------------------------------- */ const METRICS = window.METRICS; // Count-up animation for the metrics row: parses "100,000+", "5.5", "43" etc. // into a numeric target + decimal precision + suffix, then animates from 0 // once the row scrolls into view (fires once, eased, ~1.8s). function CountUpMetric({ value, style }) { const ref = React.useRef(null); const [display, setDisplay] = React.useState(null); const parsed = React.useMemo(() => { const m = String(value).match(/^([\d,]*\.?\d+)(\+?)$/); if (!m) return null; const numStr = m[1].replace(/,/g, ""); const decimals = numStr.includes(".") ? numStr.split(".")[1].length : 0; return { target: parseFloat(numStr), decimals, suffix: m[2] }; }, [value]); React.useEffect(() => { if (!parsed || !ref.current) return; setDisplay(parsed.target === Math.round(parsed.target) && parsed.decimals === 0 ? "0" : (0).toFixed(parsed.decimals)); const el = ref.current; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const duration = 1800; const start = performance.now(); const animate = (now) => { const t = Math.min((now - start) / duration, 1); const eased = 1 - Math.pow(1 - t, 3); const current = parsed.target * eased; setDisplay( parsed.decimals > 0 ? current.toFixed(parsed.decimals) : Math.round(current).toLocaleString("en-US") ); if (t < 1) requestAnimationFrame(animate); }; requestAnimationFrame(animate); observer.disconnect(); } }); }, { threshold: 0.4 }); observer.observe(el); return () => observer.disconnect(); }, [parsed]); if (!parsed) return
{value}
; return
{display}{parsed.suffix}
; } function WelcomeSection() { const wrap = { padding: "var(--space-2xl) var(--space-xl)", backgroundColor: "var(--color-surface-page)" }; const metricsRow = { display: "flex", gap: "var(--space-xl)", marginTop: "var(--space-xl)", flexWrap: "wrap", borderTop: "1px solid var(--color-border-hairline)", paddingTop: "var(--space-lg)" }; const metricValue = { fontFamily: "var(--font-serif)", fontSize: 36, color: "var(--vivaan-gold)" }; const metricLabel = { fontFamily: "var(--font-serif)", fontSize: 12, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--color-text-secondary)", marginTop: 6 }; return (
Welcome to The Vivaan Set across 5.5 acres in Karnal, The Vivaan brings together quiet luxury stays, grand celebrations and thoughtful hospitality — a private estate built for occasions that deserve to be remembered.
{METRICS.map((m) => (
{m.label}
))}
); } /* ---------------------------------------------------------------------- */ /* 3. Wedding */ /* ---------------------------------------------------------------------- */ const VENUES = window.VENUES; function VenueCard({ v }) { const [hover, setHover] = React.useState(false); return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ display: "flex", flexDirection: "column", gap: 14, cursor: "pointer" }} >
{v.images ? ( ) : ( )}
{v.name}
{v.tag}
{v.capacity}  ·  {v.area}
); } function WeddingSection() { const wrap = { padding: "var(--space-2xl) var(--space-xl)", backgroundColor: "var(--vivaan-cream)" }; const head = { display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 40, marginBottom: "var(--space-xl)", flexWrap: "wrap" }; return (
Wedding & Events Eight distinct venues, from candlelit ballrooms to open lawns beneath the sky — each one shaped around the story you want to tell.
{VENUES.map((v) => )}
); } /* ---------------------------------------------------------------------- */ /* 4. Stay */ /* ---------------------------------------------------------------------- */ const ROOMS = window.ROOMS; function StaySection() { const wrap = { padding: "var(--space-2xl) var(--space-xl)", backgroundColor: "var(--color-surface-page)", display: "flex", flexDirection: "column", gap: "var(--space-2xl)" }; const head = { display: "flex", flexDirection: "column", gap: 20, maxWidth: 560, marginBottom: "var(--space-md)" }; return (
Stay Six room categories, each composed for a different rhythm of travel — from family suites to intimate singles, every stay begins with stillness.
{[0, 1, 2].map((row) => (
{[ROOMS[row * 2], ROOMS[row * 2 + 1]].map((r) => (
{r.name}
{r.size}
    {r.usps.map((u) => (
  • {u}
  • ))}
Check Availability →
))}
))}
); } Object.assign(window, { METRICS, WelcomeSection, VENUES, WeddingSection, ROOMS, StaySection });