/* ui.jsx — shared primitives. Exports to window. Loaded first. */
const { useState, useEffect, useRef, useContext, createContext, useMemo, useCallback } = React;
/* design-system components (from the loaded bundle) */
const DS = window.OLIVEARCHDesignSystem_cfb58f || {};
const { Badge } = DS;
/* ---- language context ---------------------------------------------------- */
const LangContext = createContext("en");
const useLang = () => useContext(LangContext);
/* localized-text helper bound to current lang */
const useT = () => { const l = useLang(); return (v) => window.t(v, l); };
/* ---- icon set (Lucide-style, 1.6px stroke, currentColor) ----------------- */
const ICONS = {
"arrow-right": ,
"arrow-up-right": ,
plus: ,
menu: ,
x: ,
phone: ,
mail: <>>,
"map-pin": <>>,
globe: <>>,
filter: ,
grid: <>>,
ring: <>>,
"chevron-down": ,
"chevron-left": <>>,
images: <>>,
"chevron-right": ,
target: <>>,
shield: ,
"shield-check": <>>,
users: <>>,
clock: <>>,
compass: <>>,
medal: <>>,
"trending-up": ,
layers: ,
cpu: <>>,
"hard-hat": ,
ruler: ,
building: <>>,
hospital: <>>,
trees: ,
sofa: ,
flame: ,
leaf: ,
quote: ,
check: ,
"badge-check": <>>,
drafting: <>>,
sparkle: ,
"chevrons-right": ,
handshake: ,
award: <>>,
};
function Icon({ name, size = 20, stroke = 1.6, style, className }) {
return (
);
}
/* ---- reveal on scroll ---------------------------------------------------- */
function Reveal({ children, as = "div", delay, className = "", style, ...rest }) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (!("IntersectionObserver" in window)) { el.classList.add("is-in"); return; }
const io = new IntersectionObserver(([e]) => {
if (e.isIntersecting) { el.classList.add("is-in"); io.disconnect(); }
}, { threshold: 0.14, rootMargin: "0px 0px -6% 0px" });
io.observe(el);
return () => io.disconnect();
}, []);
const Tag = as;
return {children};
}
/* ---- overline + section header ------------------------------------------- */
function Overline({ children, style }) {
return {children};
}
/* ---- masked line-by-line headline reveal (signature load motion) --------- */
function Headline({ text, as = "h1", className = "", style, startDelay = 140, lineDelay = 95, onScroll = false }) {
const ref = useRef(null);
const lines = String(text).split("\n");
useEffect(() => {
const el = ref.current; if (!el) return;
if (window.matchMedia("(prefers-reduced-motion:reduce)").matches) { el.classList.add("in"); return; }
if (onScroll && "IntersectionObserver" in window) {
const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { el.classList.add("in"); io.disconnect(); } }, { threshold: 0.2, rootMargin: "0px 0px -8% 0px" });
io.observe(el);
return () => io.disconnect();
}
const t = setTimeout(() => el.classList.add("in"), startDelay);
return () => clearTimeout(t);
}, [text]);
const Tag = as;
return (
{lines.map((ln, i) => (
{ln || "\u00a0"}
))}
);
}
function SectionHead({ overline, title, lead, align = "left", maxWidth }) {
return (
{overline}
{lead &&
{lead}
}
);
}
/* ---- number count-up ----------------------------------------------------- */
function CountUp({ value, prefix = "", suffix = "", duration = 1700 }) {
const [n, setN] = useState(0);
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(([e]) => {
if (e.isIntersecting) {
const start = performance.now();
const step = (t) => {
const p = Math.min(1, (t - start) / duration);
setN(Math.round(value * (1 - Math.pow(1 - p, 3))));
if (p < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
io.disconnect();
}
}, { threshold: 0.5 });
io.observe(el);
return () => io.disconnect();
}, [value]);
return {prefix}{n.toLocaleString("en-IN")}{suffix};
}
/* ---- primary CTA button (DS-styled) -------------------------------------- */
function Cta({ children, onClick, variant = "primary", size = "lg", icon = "arrow-right", type }) {
const [h, setH] = useState(false);
const styles = {
primary: { background: h ? "var(--olv-gold-champagne)" : "var(--olv-gold)", color: "var(--olv-black)", border: "1px solid transparent" },
outline: { background: h ? "rgba(212,175,55,.08)" : "transparent", color: "var(--olv-gold)", border: "1px solid var(--border-gold-strong)" },
secondary: { background: "var(--surface-card-elevated)", color: h ? "var(--olv-gold-champagne)" : "var(--text-heading)", border: "1px solid var(--border-gold)" },
};
const H = { lg: 56, md: 48, sm: 40 }[size];
return (
);
}
/* ---- link button (underline reveal, reference style) --------------------- */
function LinkBtn({ children, onClick, active, plus = true, style }) {
return (
);
}
/* ---- custom cursor (fine pointers) --------------------------------------- */
function CustomCursor() {
useEffect(() => {
const fine = window.matchMedia("(hover:hover) and (pointer:fine)").matches;
if (!fine) return;
const dot = document.getElementById("cDot"), ring = document.getElementById("cRing"), torch = document.getElementById("cTorch");
if (!dot || !ring) return;
document.body.classList.add("cursor-on");
let mx = window.innerWidth / 2, my = window.innerHeight / 2, rx = mx, ry = my, tx = mx, ty = my, raf;
const move = (e) => { mx = e.clientX; my = e.clientY; dot.style.transform = `translate(${mx}px,${my}px) translate(-50%,-50%)`; };
const loop = () => { rx += (mx - rx) * 0.18; ry += (my - ry) * 0.18; ring.style.transform = `translate(${rx}px,${ry}px) translate(-50%,-50%)`; if (torch) { tx += (mx - tx) * 0.12; ty += (my - ty) * 0.12; torch.style.transform = `translate(${tx}px,${ty}px) translate(-50%,-50%)`; } raf = requestAnimationFrame(loop); };
const over = (e) => {
const t = e.target.closest("a,button,[data-cursor='grab'],input,select,textarea,label");
if (!t) { ring.style.width = "34px"; ring.style.height = "34px"; ring.style.borderColor = "var(--border-gold-strong)"; return; }
if (t.getAttribute("data-cursor") === "grab") { ring.style.width = "58px"; ring.style.height = "58px"; ring.style.borderColor = "var(--olv-gold)"; }
else { ring.style.width = "46px"; ring.style.height = "46px"; ring.style.borderColor = "var(--olv-gold)"; }
};
window.addEventListener("mousemove", move);
window.addEventListener("mouseover", over);
loop();
return () => { window.removeEventListener("mousemove", move); window.removeEventListener("mouseover", over); cancelAnimationFrame(raf); document.body.classList.remove("cursor-on"); };
}, []);
return null;
}
/* ---- scroll to a section id ---------------------------------------------- */
function scrollToId(id) {
const el = document.getElementById(id);
if (!el) return;
const y = el.getBoundingClientRect().top + window.scrollY - 64;
window.scrollTo({ top: y, behavior: "smooth" });
}
Object.assign(window, { LangContext, useLang, useT, Icon, Reveal, Overline, Headline, SectionHead, CountUp, Cta, LinkBtn, CustomCursor, scrollToId, Badge });