/* Shared primitives for Scality Solutions */
const { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect } = React;
// ---------- Lucide icon helper ----------
// Inline SVG icon paths (lucide-style, MIT). Only the ones we use.
const ICON_PATHS = {
TrendingUp: '',
UserPlus: '',
Calendar: '',
Zap: '',
ShieldCheck: '',
Star: '',
Code2: '',
Layers: '',
Workflow: '',
ArrowRight: '',
ArrowLeft: '',
ChevronDown: '',
ChevronLeft: '',
ChevronRight: '',
Check: '',
CheckCircle2: '',
Menu: '',
X: '',
Mail: '',
Phone: '',
FileText: '',
Linkedin: '',
Instagram: '',
Facebook: '',
User: '',
LayoutDashboard: '',
Users: '',
Settings: '',
Bell: '',
Search: '',
Home: '',
ClipboardList: '',
CalendarDays: '',
Smartphone: '',
WifiOff: '',
Lock: '',
Wifi: '',
Battery: '',
Signal: '',
MapPin: '',
Clock: '',
Plus: '',
};
function makeIcon(name) {
return function Icon({ size = 18, className = "", strokeWidth = 1.8, style = {}, fill }) {
const path = ICON_PATHS[name];
if (!path) return ;
return (
);
};
}
const TrendingUp = makeIcon("TrendingUp");
const UserPlus = makeIcon("UserPlus");
const Calendar = makeIcon("Calendar");
const Zap = makeIcon("Zap");
const ShieldCheck = makeIcon("ShieldCheck");
const Star = makeIcon("Star");
const Code2 = makeIcon("Code2");
const Layers = makeIcon("Layers");
const Workflow = makeIcon("Workflow");
const ArrowRight = makeIcon("ArrowRight");
const ArrowLeft = makeIcon("ArrowLeft");
const ChevronDown = makeIcon("ChevronDown");
const ChevronLeft = makeIcon("ChevronLeft");
const ChevronRight = makeIcon("ChevronRight");
const Check = makeIcon("Check");
const CheckCircle2 = makeIcon("CheckCircle2");
const Menu = makeIcon("Menu");
const X = makeIcon("X");
const Mail = makeIcon("Mail");
const Phone = makeIcon("Phone");
const FileText = makeIcon("FileText");
const Linkedin = makeIcon("Linkedin");
const Instagram = makeIcon("Instagram");
const Facebook = makeIcon("Facebook");
const User = makeIcon("User");
const LayoutDashboard = makeIcon("LayoutDashboard");
const Briefcase = makeIcon("Briefcase");
const Users = makeIcon("Users");
const Settings = makeIcon("Settings");
const Bell = makeIcon("Bell");
const Search = makeIcon("Search");
const Home = makeIcon("Home");
const ClipboardList = makeIcon("ClipboardList");
const CalendarDays = makeIcon("CalendarDays");
const Smartphone = makeIcon("Smartphone");
const WifiOff = makeIcon("WifiOff");
const Lock = makeIcon("Lock");
const Wifi = makeIcon("Wifi");
const Battery = makeIcon("Battery");
const Signal = makeIcon("Signal");
const MapPin = makeIcon("MapPin");
const Clock = makeIcon("Clock");
const Globe = makeIcon("Globe");
const Cog = makeIcon("Cog");
const Target = makeIcon("Target");
const Plus = makeIcon("Plus");
const Minus = makeIcon("Minus");
const Award = makeIcon("Award");
// ---------- Reveal hook ----------
function useReveal(threshold = 0.1) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
el.classList.add("in");
io.unobserve(el);
}
});
},
{ threshold, rootMargin: "0px 0px -10% 0px" }
);
io.observe(el);
return () => io.disconnect();
}, [threshold]);
return ref;
}
// ---------- Buttons ----------
function PrimaryButton({ children, href = "#kontakt", onClick, className = "", magnet = true, size = "md" }) {
const ref = useRef(null);
useEffect(() => {
if (!magnet) return;
const el = ref.current;
if (!el) return;
const onMove = (e) => {
const r = el.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
const dx = (e.clientX - cx) / (r.width / 2);
const dy = (e.clientY - cy) / (r.height / 2);
const max = 8;
el.style.transform = `translate(${Math.max(-1,Math.min(1,dx))*max}px, ${Math.max(-1,Math.min(1,dy))*max}px) scale(1.02)`;
};
const onLeave = () => { el.style.transform = ""; };
const region = el;
region.addEventListener("mousemove", onMove);
region.addEventListener("mouseleave", onLeave);
return () => {
region.removeEventListener("mousemove", onMove);
region.removeEventListener("mouseleave", onLeave);
};
}, [magnet]);
const sizes = {
sm: "px-4 py-2 text-sm",
md: "px-6 py-3 text-[15px]",
lg: "px-7 py-4 text-base",
};
return (
{children}
);
}
function GhostButton({ children, href = "#projekte", onClick, className = "", size = "md" }) {
const sizes = {
sm: "px-4 py-2 text-sm",
md: "px-6 py-3 text-[15px]",
lg: "px-7 py-4 text-base",
};
return (
{children}
);
}
// ---------- Stats count-up ----------
function CountUp({ to, suffix = "", prefix = "", duration = 1600, decimals = 0 }) {
const [val, setVal] = useState(0);
const ref = useRef(null);
const startedRef = useRef(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting && !startedRef.current) {
startedRef.current = true;
const start = performance.now();
const tick = (t) => {
const p = Math.min(1, (t - start) / duration);
// easeOutCubic
const e2 = 1 - Math.pow(1 - p, 3);
setVal(to * e2);
if (p < 1) requestAnimationFrame(tick);
else setVal(to);
};
requestAnimationFrame(tick);
}
});
}, { threshold: 0.4 });
io.observe(el);
return () => io.disconnect();
}, [to, duration]);
const formatted = decimals ? val.toFixed(decimals) : Math.round(val).toString();
return {prefix}{formatted}{suffix};
}
// expose
Object.assign(window, {
TrendingUp, UserPlus, Calendar, Zap, ShieldCheck, Star, Code2, Layers, Workflow,
ArrowRight, ArrowLeft, ChevronDown, ChevronLeft, ChevronRight, Check, CheckCircle2,
Menu, X, Mail, Phone, FileText, Linkedin, Instagram, Facebook, User,
LayoutDashboard, Briefcase, Users, Settings, Bell, Search, Home, ClipboardList,
CalendarDays, Smartphone, WifiOff, Lock, Wifi, Battery, Signal, MapPin, Clock,
Globe, Cog, Target, Plus, Minus, Award,
useReveal, PrimaryButton, GhostButton, CountUp, makeIcon,
});