Initial commit: UP Parivaar Dallas site

This commit is contained in:
2026-07-23 20:08:08 +05:30
commit 9a42e8b6e5
128 changed files with 8409 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
"use client";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
// Selectors for elements that should animate in on scroll (scoped to <main>).
const TARGETS = [
"h1", "h2", "h3", "h4",
".eyebrow", ".eyebrow-light",
"p",
".btn-primary", ".btn-ghost", ".link-arrow", ".chip",
'[class*="card"]', '[class*="__tile"]', '[class*="__item"]',
'[class*="__pillar"]', '[class*="__step"]', '[class*="__cat"]',
]
.map((s) => `main ${s}`)
.join(",");
// Sections that manage their own motion / must not be hidden.
const EXCLUDE = ".home-hero, .jbanner, .nav, .footer, nav, footer, header";
export default function ScrollReveal() {
const pathname = usePathname();
useEffect(() => {
if (typeof window === "undefined") return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let cleanup = () => {};
const id = window.requestAnimationFrame(() => {
const all = Array.from(document.querySelectorAll<HTMLElement>(TARGETS)).filter(
(el) => !el.closest(EXCLUDE)
);
// Keep only the outermost targets so a card animates as one unit
// (its inner heading/paragraph don't animate separately).
const set = new Set(all);
const targets = all.filter((el) => {
let p = el.parentElement;
while (p) {
if (set.has(p)) return false;
p = p.parentElement;
}
return true;
});
// Small stagger between siblings that share a parent.
const byParent = new Map<Element, HTMLElement[]>();
targets.forEach((el) => {
const parent = el.parentElement;
if (!parent) return;
const arr = byParent.get(parent) ?? [];
arr.push(el);
byParent.set(parent, arr);
});
byParent.forEach((arr) => {
arr.forEach((el, idx) => {
el.style.setProperty("--reveal-delay", `${Math.min(idx * 0.08, 0.4)}s`);
});
});
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
}
});
},
{ rootMargin: "0px 0px -8% 0px", threshold: 0.08 }
);
const variantFor = (el: HTMLElement) => {
const cls = el.className || "";
if (el.matches(".eyebrow, .eyebrow-light")) return "reveal--left";
if (
/card|__tile|__item|__pillar|__step|__cat/.test(
typeof cls === "string" ? cls : ""
)
) {
return "reveal--zoom";
}
return "reveal--up";
};
targets.forEach((el) => {
el.classList.add("reveal", variantFor(el));
const rect = el.getBoundingClientRect();
const inView = rect.top < window.innerHeight && rect.bottom > 0;
if (inView) {
// Already visible on load — reveal instantly, no hidden flash.
el.classList.add("is-visible");
} else {
observer.observe(el);
}
});
cleanup = () => observer.disconnect();
});
return () => {
window.cancelAnimationFrame(id);
cleanup();
};
}, [pathname]);
return null;
}