/* 慶宜僱傭代理 — UI kit shared primitives, chrome & visual meta (browser-babel)
Copy lives in i18n.jsx (window.HY_I18N). This file holds components + the
visual meta (icons / tones / dot colours) that pairs with that copy. */
/* ---------------- Icon (Lucide) ---------------- */
function Icon({ n, style }) { return ; }
function refreshIcons() { if (window.lucide) window.lucide.createIcons(); }
/* ---------------- Visual meta (parallel-indexed to i18n copy) ---------------- */
const SVC_META = [
{ i: "plane", tone: "neutral" }, { i: "home", tone: "success" }, { i: "file-text", tone: "neutral" },
{ i: "baby", tone: "gold" }, { i: "heart-handshake", tone: "gold" }, { i: "graduation-cap", tone: "gold" },
{ i: "refresh-cw", tone: "neutral" }, { i: "shield-check", tone: "neutral" }, { i: "stamp", tone: "solid-gold" },
];
const REGION_DOTS = ["var(--c-or)", "var(--c-or)", "var(--c-or)"];
const TRUST_ICONS = ["badge-check", "stamp", "receipt-text", "handshake"];
const tileMod = (i) => i % 3 === 1 ? " icotile--gold" : i % 3 === 2 ? " icotile--lime" : "";
/* ---------------- Wordmark ---------------- */
function Wordmark({ dark = false, mono = false }) {
return (
e.preventDefault()} href="#">
慶
{!mono && (
慶宜僱傭代理
Hing Yee · Est. 1985
)}
);
}
/* ---------------- Primitives ---------------- */
/* WhatsApp deep link (all WhatsApp CTAs point here) */
const WA_NUMBER = "85244127906";
const WA_LINK = "https://wa.me/" + WA_NUMBER;
function waLink(t) {
const msg = (t && t.waMsg) || "";
return msg ? `${WA_LINK}?text=${encodeURIComponent(msg)}` : WA_LINK;
}
function Btn({ children, variant = "primary", size = "", icon, iconRight, href, external, onClick, ...rest }) {
const cls = ["btn", `btn--${variant}`, size ? `btn--${size}` : ""].filter(Boolean).join(" ");
const handle = (e) => { if (!href) e.preventDefault(); if (onClick) onClick(e); };
return (
{icon && }
{children && {children}}
{iconRight && }
);
}
function Badge({ children, tone = "neutral", dot }) {
return {dot && }{children};
}
/* ---------------- Section header ---------------- */
function SectionHead({ eyebrow, title, lede, center }) {
return (
{eyebrow &&
{eyebrow}}
{lede &&
{lede}
}
);
}
/* ---------------- Photo ---------------- */
function Photo({ label, style, src, alt, children }) {
if (src) {
return (

{children}
);
}
return (
{label}
{children}
);
}
/* ---------------- Nav ---------------- */
function SiteNav({ page, setPage, lang, setLang, t }) {
const [open, setOpen] = React.useState(false);
const items = [["home", t.nav.home], ["about", t.nav.about], ["database", t.nav.database], ["cases", t.nav.cases], ["process", t.nav.process], ["blog", t.nav.blog], ["contact", t.nav.contact]];
const go = (id) => { setPage(id); setOpen(false); };
return (
);
}
/* ---------------- Helper card (shared: carousel + database) ---------------- */
function HelperCard({ p, t, onBook }) {
const H = t.helperSection;
return (
{p.years} {H.yrsLabel}
{p.rating}
{p.nat &&
{p.nat}}
{p.name} {p.age}
{p.skills.map((s) => {s})}
{p.blurb}
{H.cta}
);
}
/* ---------------- Trust bar ---------------- */
function TrustBar({ t }) {
return (
{t.trust.map((it, i) => (
{it.t}{it.s}
))}
);
}
/* ---------------- CTA band ---------------- */
function CTABand({ setPage, t }) {
return (
{t.cta.title}
{t.cta.p}
{t.cta.btn1}
{t.cta.btn2}
);
}
/* ---------------- Footer ---------------- */
function SiteFooter({ setPage, t }) {
const f = t.footer;
return (
);
}
/* ---------------- WhatsApp float ---------------- */
function WhatsApp({ t }) {
return ;
}
/* ---------------- Helper showcase carousel ---------------- */
function HelperCarousel({ t, setPage }) {
const H = t.helperSection;
const onBook = () => setPage && setPage("contact");
const railRef = React.useRef(null);
const scroll = (dir) => {
const el = railRef.current; if (!el) return;
const card = el.querySelector(".hcard");
const w = card ? card.offsetWidth + 18 : 320;
const start = el.scrollLeft;
const target = Math.max(0, Math.min(start + dir * w, el.scrollWidth - el.clientWidth));
el.style.scrollSnapType = "none";
const dur = 360, t0 = Date.now();
const ease = (p) => 1 - Math.pow(1 - p, 3);
const id = setInterval(() => {
const p = Math.min(1, (Date.now() - t0) / dur);
el.scrollLeft = start + (target - start) * ease(p);
if (p >= 1) { clearInterval(id); el.scrollLeft = target; el.style.scrollSnapType = ""; }
}, 16);
};
return (
{H.eyebrow}
{H.title}
{H.lede}
{t.helpers.map((p, i) => (
))}
);
}
/* ---------------- Why-us (data + icons) ---------------- */
function WhyUs({ t }) {
const W = t.ext.whyus;
return (
{W.items.map((it, i) => (
))}
);
}
/* ---------------- Process timeline ---------------- */
function ProcessTimeline({ t, compact }) {
const P = t.ext.process;
const icons = ["phone-call", "user-search", "calendar-check", "file-text", "plane-landing", "life-buoy"];
return (
{P.steps.map((s, i) => (
{String(i + 1).padStart(2, "0")}
{s.t}
{s.d}
))}
);
}
/* ---------------- Cases grid ---------------- */
function CasesGrid({ t, limit }) {
const C = t.ext;
const list = limit ? C.cases.slice(0, limit) : C.cases;
return (
{list.map((c, i) => (
{C.caseFields.needL}{c.need}
{C.caseFields.resultL}{c.result}
「{c.quote}」— {c.who}
))}
);
}
/* ---------------- Google-style reviews ---------------- */
function GoogleReviews({ t }) {
const R = t.ext.reviews, list = t.ext.reviewList;
return (
{list.map((r, i) => (
{"★".repeat(r.stars)}{"★".repeat(5 - r.stars)}
{r.text}
))}
);
}
Object.assign(window, {
Icon, refreshIcons, Wordmark, Btn, Badge, SectionHead, Photo, waLink, WA_LINK,
SiteNav, TrustBar, CTABand, SiteFooter, WhatsApp, HelperCarousel, HelperCard,
WhyUs, ProcessTimeline, CasesGrid, GoogleReviews,
SVC_META, REGION_DOTS, TRUST_ICONS, tileMod,
});