/* global React */ // Shared UI primitives for LynkedUp Pro const { useEffect, useRef, useState } = React; // ---------- Hex logo (matches flyer mark) ---------- function Logo({ size = 90 }) { return (
LynkedUp Pro
); } // ---------- 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 ( {/* Floor grid */} {/* House outline - two-gable */} {/* Main body */} {/* Right side smaller gable */} {/* Roof ridge */} {/* Chimney */} {/* Door */} {/* Windows */} {/* Roof structure lines */} {/* Measurements */} {withMeasurements && ( 18′-7″ 12′-3″ 15′-4″ 8′-0″ )} {/* Corner markers */} {[[60,200],[210,90],[360,200],[450,180],[540,240]].map(([x,y],i) => ( ))} {/* AI scan line */} {withScan && ( )} ); } // ---------- 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 ( {segments.map((s, i) => { const len = (s.value / total) * c; const el = ( ); offset += len; return el; })} ); } // ---------- 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 (
{isH ? ( <> ) : ( <> )} {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 (
{number} {tag}
{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 });