/* global React */
const { useState, useEffect, useRef } = React;
/* ----------------------------------------------------------------------------
Icon — inline Lucide-style paths (1.75 stroke, round).
-------------------------------------------------------------------------- */
const ICONS = {
arrowRight: ,
arrowUpRight: ,
chevronRight: ,
chevronDown: ,
activity: ,
shieldCheck: <>>,
users: <>>,
trendingUp: <>>,
barChart: <>>,
lineChart: <>>,
mapPin: <>>,
target: <>>,
handshake: <>>,
building: <>>,
landmark: <>>,
phone: ,
message: ,
check: ,
menu: <>>,
x: <>>,
heartPulse: <>>,
heart: ,
clipboardCheck: <>>,
fileText: <>>,
globe: <>>,
award: <>>,
sparkles: <>>,
layers: <>>,
compass: <>>,
lock: <>>,
calendar: <>>,
linkedin: <>>,
twitter: ,
facebook: ,
youtube: <>>,
mail: <>>,
stethoscope: <>>,
database: <>>,
image: <>>,
};
function Icon({ name, size = 22, stroke = 1.75, className = "", style = {} }) {
return (
);
}
/* ---- Button ------------------------------------------------------------- */
function Button({ children, variant = "primary", size = "md", icon = "arrowRight", showIcon = false, onClick, href, type, className = "" }) {
const cls = `hb hb-${variant} hb-${size} ${className}`;
const inner = <>{children}{showIcon && icon && }>;
if (href) return {inner};
return ;
}
function Eyebrow({ children, color }) {
return {children};
}
function SectionHead({ eyebrow, title, lead, align = "left", eyebrowColor, max }) {
return (
{eyebrow && {eyebrow}}
{title && {title}
}
{lead && {lead}
}
);
}
/* ---- Reveal: scroll-in animation (respects reduced motion) -------------- */
function Reveal({ children, className = "", style = {}, as = "div", delay = 0, y = 22 }) {
const ref = useRef(null);
const [seen, setSeen] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setSeen(true); return; }
// Already in view on mount? Reveal immediately (covers above-the-fold + no-IO contexts).
const r = el.getBoundingClientRect();
if (r.top < window.innerHeight * 0.95 && r.bottom > 0) { setSeen(true); return; }
let fallback = 0;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); clearTimeout(fallback); } });
}, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
io.observe(el);
// Safety net: content must never stay hidden if IO never fires.
fallback = setTimeout(() => { setSeen(true); io.disconnect(); }, 1200);
return () => { io.disconnect(); clearTimeout(fallback); };
}, []);
const Tag = as;
return (
{children}
);
}
/* ---- Parallax: subtle scroll translate for decorative / media ----------- */
function useParallax(factor = 0.12) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let raf = 0;
const onScroll = () => {
if (raf) return;
raf = requestAnimationFrame(() => {
raf = 0;
const r = el.getBoundingClientRect();
const center = r.top + r.height / 2 - window.innerHeight / 2;
el.style.transform = `translate3d(0, ${(-center * factor).toFixed(1)}px, 0)`;
});
};
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
return () => window.removeEventListener("scroll", onScroll);
}, [factor]);
return ref;
}
/* ---- Img: real photo in a rounded frame with rollover zoom -------------- */
function Img({ src, alt = "", ratio, className = "", parallax = 0, imgClass = "", style = {}, eager = false }) {
const pref = useParallax(parallax);
return (
);
}
/* Photo placeholder kept for back-compat */
function Photo({ label, ratio = "4 / 3", className = "", style = {} }) {
return (
{label}
);
}
/* ---- CountUp: animate a number to its target when scrolled into view ----- */
function CountUp({ to, prefix = "", suffix = "", decimals = 0, format = false, dur = 1500 }) {
const ref = useRef(null);
const [val, setVal] = useState(0);
const started = useRef(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const run = () => {
if (started.current) return;
started.current = true;
const start = performance.now();
const tick = (now) => {
const p = Math.min(1, (now - start) / dur);
const eased = 1 - Math.pow(1 - p, 3);
setVal(to * eased);
if (p < 1) requestAnimationFrame(tick); else setVal(to);
};
requestAnimationFrame(tick);
};
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setVal(to); return; }
const r = el.getBoundingClientRect();
if (r.top < window.innerHeight * 0.92 && r.bottom > 0) { run(); return; }
const io = new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) { run(); io.disconnect(); } }), { threshold: 0.3 });
io.observe(el);
const fb = setTimeout(run, 1600);
return () => { io.disconnect(); clearTimeout(fb); };
}, [to]);
const num = format ? Math.round(val).toLocaleString() : (decimals ? val.toFixed(decimals) : Math.round(val).toString());
return {prefix}{num}{suffix};
}
/* ---- ImpactBand: capabilities section, reused on every page ------------- */
const IMPACT_CAPS = [
{ ic: "users", t: "Population health management" },
{ ic: "award", t: "HEDIS and Star Ratings support" },
{ ic: "trendingUp", t: "Quality improvement" },
{ ic: "target", t: "Care gap closure" },
{ ic: "heart", t: "Member engagement and retention" },
{ ic: "barChart", t: "Risk adjustment support" },
{ ic: "compass", t: "Health equity strategy" },
{ ic: "lineChart", t: "Value-based care performance" },
{ ic: "mapPin", t: "Community-based outreach" },
{ ic: "database", t: "Data-informed reporting and measurement" },
];
function ImpactBand({ video = "assets/video/middle-2.mp4" }) {
return (
What we do
Intelligence-driven. Human-centered. Built to close gaps.
HCDI helps clients identify priority populations, understand barriers to care,
and activate outreach strategies that move people toward better health.
Our work supports
{IMPACT_CAPS.map((c, i) => (
{c.t}
))}
);
}
Object.assign(window, { Icon, Button, Eyebrow, SectionHead, Reveal, useParallax, Img, Photo, CountUp, ImpactBand, useState, useEffect, useRef });