/* global React */
// Shared UI primitives for LynkedUp Pro
const { useEffect, useRef, useState } = React;
// ---------- Hex logo (matches flyer mark) ----------
function Logo({ size = 90 }) {
return (
);
}
// ---------- Counter that animates on view ----------
function Counter({ to, suffix = "", prefix = "", duration = 1800, decimals = 0 }) {
const ref = useRef(null);
const [val, setVal] = useState(0);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
const start = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - start) / duration);
const ease = 1 - Math.pow(1 - t, 3);
setVal(to * ease);
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
obs.disconnect();
}
});
}, { threshold: 0.3 });
obs.observe(el);
return () => obs.disconnect();
}, [to]);
const formatted = decimals
? val.toFixed(decimals)
: Math.round(val).toLocaleString();
return {prefix}{formatted}{suffix};
}
// ---------- Reveal-on-scroll wrapper ----------
function Reveal({ children, delay = 0, className = "", as: As = "div", style = {} }) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
el.classList.add("in");
obs.unobserve(el);
}
});
}, { threshold: 0.12, rootMargin: "0px 0px -60px 0px" });
obs.observe(el);
return () => obs.disconnect();
}, []);
const delayCls = delay ? ` delay-${delay}` : "";
return (
{children}
);
}
// ---------- Card with mouse-tracking glow ----------
function GlowCard({ children, className = "", style = {}, onClick }) {
const ref = useRef(null);
const handle = (e) => {
const el = ref.current;
if (!el) return;
const r = el.getBoundingClientRect();
el.style.setProperty("--mx", `${e.clientX - r.left}px`);
el.style.setProperty("--my", `${e.clientY - r.top}px`);
};
return (
{children}
);
}
// ---------- Wireframe Blueprint House (SVG, animated drawing) ----------
function BlueprintHouse({ size = 600, animate = true, withMeasurements = true, withScan = true }) {
// viewBox 600x460
const dash = animate ? { strokeDasharray: 1400, strokeDashoffset: 1400, animation: "drawIn 4s ease-out forwards" } : {};
return (
);
}
// ---------- Tiny chart components ----------
function Sparkline({ data, color = "#00d1ff", height = 36, width = 120 }) {
const max = Math.max(...data), min = Math.min(...data);
const range = max - min || 1;
const stepX = width / (data.length - 1);
const pts = data.map((v, i) => `${i * stepX},${height - ((v - min) / range) * height}`).join(" ");
const area = `0,${height} ${pts} ${width},${height}`;
return (
);
}
function DonutChart({ size = 120, stroke = 12, segments }) {
// segments: [{value, color}]
const r = (size - stroke) / 2;
const c = 2 * Math.PI * r;
const total = segments.reduce((s, x) => s + x.value, 0);
let offset = 0;
return (
);
}
// ---------- Icon set (line, glowing) ----------
const Icon = ({ name, size = 22, stroke = "#7be8ff" }) => {
const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke, strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", style: { filter: `drop-shadow(0 0 6px ${stroke}55)` } };
switch (name) {
case "ai":
return ;
case "calendar":
return ;
case "crew":
return ;
case "fleet":
return ;
case "budget":
return ;
case "report":
return ;
case "chat":
return ;
case "field":
return ;
case "shield":
return ;
case "clock":
return ;
case "target":
return ;
case "growth":
return ;
case "bell":
return ;
case "pin":
return ;
case "sync":
return ;
case "brain":
return ;
case "doc":
return ;
case "camera":
return ;
case "hammer":
return ;
case "lead":
return ;
case "money":
return ;
case "spark":
return ;
case "arrow":
return ;
case "play":
return ;
default:
return ;
}
};
// ---------- Dimension corner brackets (blueprint chrome) ----------
function DimCorner({ pos = "tl" }) {
return (
);
}
// ---------- Dimension line (engineering measurement) ----------
function DimLine({ orientation = "h", label, length = "100%", offset = 0, side = "top" }) {
const isH = orientation === "h";
return (
{label && (
{label}
)}
);
}
// ---------- SectionFrame: standard architectural-framed section ----------
function SectionFrame({ children, number, tag, vlabel, id, className = "", style = {} }) {
return (
{vlabel && (
<>
{vlabel}
>
)}
{(number || tag) && (
{number && (
§{number}
{tag}
)}
// {vlabel || "BUILD MK.07"}
)}
{children}
);
}
// ---------- Section head with rail (number + tag + ticks) ----------
function SectionHead({ number, tag, title, sub, align = "left" }) {
return (
{typeof title === "string" ? {title}
: title}
{sub && (
{sub}
)}
);
}
// Export to window for cross-script access
Object.assign(window, { Logo, Counter, Reveal, GlowCard, BlueprintHouse, Sparkline, DonutChart, Icon, DimCorner, DimLine, SectionFrame, SectionHead });