// components.jsx — shared primitives for KVB 2.0
const { useState, useEffect, useRef } = React;
/* ---------------- Icons (clean line set) ---------------- */
const I = {
bolt: "M13 2 4 14h6l-1 8 9-12h-6l1-8z",
shield:"M12 3 5 6v5c0 4.4 3 7.6 7 9 4-1.4 7-4.6 7-9V6l-7-3z",
eye: "M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z",
chart: "M4 20V10M10 20V4M16 20v-7M22 20H2",
chat: "M4 5h16v11H9l-5 4V5z",
users: "M16 19v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM22 19v-2a4 4 0 0 0-3-3.9M16 3.1A4 4 0 0 1 16 11",
check: "M20 6 9 17l-5-5",
arrow: "M5 12h14M13 6l6 6-6 6",
play: "M7 4v16l13-8z",
lock: "M6 10V7a6 6 0 1 1 12 0v3M5 10h14v11H5z",
globe: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM3 12h18M12 3c2.5 2.5 4 6 4 9s-1.5 6.5-4 9c-2.5-2.5-4-6-4-9s1.5-6.5 4-9z",
spark: "M12 3v4M12 17v4M3 12h4M17 12h4M6.3 6.3l2.4 2.4M15.3 15.3l2.4 2.4M17.7 6.3l-2.4 2.4M8.7 15.3l-2.4 2.4",
rocket:"M5 15c-1 1-2 5-2 5s4-1 5-2M9 12l3 3M14 4c4 1 6 3 7 7-3 3-7 4-10 3-1.5-2.5-1.5-7 3-10zM9 12c-1.5 0-3 .5-4 2",
gauge: "M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM12 12l4-4M5 19a8 8 0 1 1 14 0",
card: "M3 6h18v12H3zM3 10h18",
grid: "M4 4h7v7H4zM13 4h7v7h-7zM13 13h7v7h-7zM4 13h7v7H4z",
cpu: "M6 6h12v12H6zM9 9h6v6H9zM9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3",
signal:"M4 18v-3M9 18v-7M14 18v-11M19 18V4",
headset:"M4 14v-2a8 8 0 0 1 16 0v2M4 14a2 2 0 0 0 2 2h1v-5H6a2 2 0 0 0-2 2zM20 14a2 2 0 0 1-2 2h-1v-5h1a2 2 0 0 1 2 2zM18 16v1a4 4 0 0 1-4 4h-2",
refresh:"M3 12a9 9 0 0 1 15-6.7L21 8M21 3v5h-5M21 12a9 9 0 0 1-15 6.7L3 16M3 21v-5h5",
video: "M3 6h12v12H3zM15 10l6-3v10l-6-3",
code: "M9 8l-4 4 4 4M15 8l4 4-4 4",
phone: "M6 3h12v18H6zM10 18h4",
poll: "M5 21V9M12 21V3M19 21v-7",
scissors: "M6 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM8.5 8.5 20 18M8.5 15.5 20 6M12 12l2 1.5",
zap: "M11 2 4 13h6l-1 9 8-12h-6l1-8z",
clock: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM12 7v5l3 2",
star: "M12 3l2.6 5.6 6.1.7-4.5 4.2 1.2 6-5.4-3-5.4 3 1.2-6L3.3 9.3l6.1-.7z",
heart: "M12 20s-7-4.5-9.5-9C1 8 2.5 4.5 6 4.5c2 0 3.2 1.2 4 2.3.8-1.1 2-2.3 4-2.3 3.5 0 5 3.5 3.5 6.5C19 15.5 12 20 12 20z",
trophy: "M7 4h10v4a5 5 0 0 1-10 0V4zM7 6H4v1a3 3 0 0 0 3 3M17 6h3v1a3 3 0 0 1-3 3M9 16h6M8 21h8M12 18v3",
calendar: "M4 6h16v15H4zM4 10h16M8 3v4M16 3v4",
flag: "M5 21V4M5 4h11l-2 4 2 4H5",
book: "M5 4h11a3 3 0 0 1 3 3v13a2 2 0 0 0-2-2H5zM5 4v14",
plus: "M12 5v14M5 12h14",
target: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM12 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2z",
message: "M21 12a8 8 0 0 1-11.5 7.2L3 21l1.8-6.5A8 8 0 1 1 21 12z",
mic: "M12 3a3 3 0 0 1 3 3v6a3 3 0 0 1-6 0V6a3 3 0 0 1 3-3zM5 11a7 7 0 0 0 14 0M12 18v3",
};
function Icon({ name, size = 22, stroke = 1.8, fill = false, style }) {
const d = I[name] || I.spark;
return (
{d.split("M").filter(Boolean).map((seg, i) => )}
);
}
/* ---------------- Logo (KVB.GG wordmark) ---------------- */
function Logo({ size = 30, onClick }) {
// `size` = rendered height in px; width follows the 131:37 aspect ratio.
const h = Math.round(size * 0.84);
return (
);
}
/* ---------------- Reveal ----------------
Passthrough wrapper: content is always rendered fully visible. We
deliberately avoid opacity/transform entrance animations because the
preview/runtime can freeze CSS animations at their first keyframe,
which would strand content at opacity:0. Layout-only wrapper. */
function Reveal({ children, delay = 0, as: Tag = "div", style, className = "" }) {
return {children} ;
}
/* ---------------- Top nav (floating glass) ---------------- */
// ---- ONE nav for the whole public site -------------------------------------
// Targets: "#id" = a section on the landing page, ">route" = a real page.
// Both TopNav (landing) and PageHeader (sub-pages) render THIS list, so the nav never
// changes identity as you move around.
const SITE_LINKS = [
["Features", "#features"],
["Pricing", "#pricing"],
["AI Chatbot", ">ai-chatbot"],
["KCIP Fix", ">kcip"],
["FAQ", "#faq"],
["Support", ">discord"],
];
// Shared click handler. A section link works from ANY page: if we're not on the landing
// page we route there first, then scroll once React has painted it.
function siteNavGo(target, route, onNav, closeMenu) {
return (e) => {
if (e && e.preventDefault) e.preventDefault();
if (closeMenu) closeMenu();
if (target.charAt(0) === ">") { onNav(target.slice(1)); return; }
const id = target.slice(1);
const scroll = () => document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
if (route === "landing") { scroll(); return; }
onNav("landing");
// onNav jumps to the top; wait for the landing tree to mount before scrolling.
setTimeout(scroll, 140);
};
}
function siteNavActive(target, route) {
return target.charAt(0) === ">" && target.slice(1) === route;
}
function TopNav({ onNav, onLogin, route }) {
const [scrolled, setScrolled] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
const fn = () => setScrolled(window.scrollY > 24);
window.addEventListener("scroll", fn, { passive: true }); fn();
return () => window.removeEventListener("scroll", fn);
}, []);
const links = SITE_LINKS;
const go = (target) => siteNavGo(target, route, onNav, () => setMenuOpen(false));
return (
onNav("landing")} />
{links.map(([label, target]) => (
" ? "#" : target} onClick={go(target)} style={{
padding: "8px 14px", borderRadius: 999, fontSize: 14, fontWeight: 500, color: "var(--ink-2)",
transition: "color .2s, background .2s",
}}
onMouseEnter={(e) => { e.currentTarget.style.color = "var(--ink)"; e.currentTarget.style.background = "var(--glass-fill)"; }}
onMouseLeave={(e) => { e.currentTarget.style.color = "var(--ink-2)"; e.currentTarget.style.background = "transparent"; }}>
{label}
))}
Log in
onNav("signup")}>
Get KVB
setMenuOpen(o => !o)} style={{
display: "none", width: 40, height: 40, borderRadius: 999, border: "1px solid var(--glass-border)",
background: "var(--glass-fill)", color: "var(--ink)", cursor: "pointer", alignItems: "center", justifyContent: "center", flexDirection: "column", gap: 4,
}}>
{/* Mobile dropdown */}
{ setMenuOpen(false); onLogin(); }} style={{ flex: 1, justifyContent: "center" }}>Log in
{ setMenuOpen(false); onNav("signup"); }} style={{ flex: 1, justifyContent: "center" }}>Get KVB
);
}
/* ---------------- Footer ---------------- */
const FOOTER_ROUTES = {
"Features": { id: "features" }, "Performance": { id: "perf" }, "Pricing": { id: "pricing" },
"Dashboard": { route: "dashboard" }, "AI Chatbot": { route: "ai-chatbot" },
"About": { route: "about" }, "Support": { route: "discord" }, "Status": { route: "status" }, "Changelog": { route: "changelog" },
"Privacy Policy": { route: "privacy" }, "Refund Policy": { route: "refund" }, "Terms of Service": { route: "terms" },
};
// The footer badge sits on EVERY page, so a hardcoded "All systems operational" is the site's
// most-seen claim. It reads the same feed as the status page (30s server-side cache, so this is
// cheap) and says nothing at all until it has an answer — a neutral label beats a confident lie.
function FooterStatusChip({ onNav }) {
const [s, setS] = React.useState(null);
React.useEffect(() => {
let alive = true;
fetch("/app/api/status.php", { cache: "no-store" })
.then(r => (r.ok ? r.json() : null))
.then(j => { if (alive && j && j.services) setS(j); })
.catch(() => { /* leave the neutral label */ });
return () => { alive = false; };
}, []);
const label = !s
? "System status"
: s.all_ok ? "All systems operational" : `${s.operational} of ${s.total} systems operational`;
const tone = !s || s.all_ok
? null
: ((s.services || []).some(x => x.status === "outage") ? "oklch(0.65 0.20 25)" : "oklch(0.82 0.15 75)");
return (
onNav("status")} title="View full system status"
style={{ marginTop: 18, cursor: "pointer", border: "1px solid var(--glass-border-soft)" }}>
{label}
);
}
function Footer({ onNav }) {
const cols = [
["Product", ["Features", "AI Chatbot", "Pricing", "Dashboard"]],
["Company", ["About", "Support", "Status", "Changelog"]],
["Legal", ["Privacy Policy", "Refund Policy", "Terms of Service"]],
];
const handle = (x) => (e) => {
e.preventDefault();
const t = FOOTER_ROUTES[x];
if (!t) return;
if (t.route) onNav(t.route);
else if (t.id) { onNav("landing"); setTimeout(() => document.getElementById(t.id)?.scrollIntoView({ behavior: "smooth", block: "start" }), 60); }
};
return (
onNav("landing")} />
KVB is the best view bot on the market in terms of both price and performance. We strive to bring our customers the highest quality with the lowest prices.
{cols.map(([h, items]) => (
))}
© 2026 KVB · All rights reserved
Made for Creators who move at the speed of Light.
);
}
/* ---------------- Stat counter ---------------- */
function Stat({ value, suffix = "", label, decimals = 0 }) {
const ref = useRef(null);
const [n, setN] = useState(0);
useEffect(() => {
const el = ref.current; if (!el) return;
const io = new IntersectionObserver(([e]) => {
if (!e.isIntersecting) return; io.disconnect();
const dur = 1400, t0 = performance.now();
const tick = (t) => {
const p = Math.min(1, (t - t0) / dur);
const e2 = 1 - Math.pow(1 - p, 3);
setN(value * e2);
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}, { threshold: 0.4 });
io.observe(el); return () => io.disconnect();
}, [value]);
const disp = decimals ? n.toFixed(decimals) : Math.round(n).toLocaleString();
return (
);
}
Object.assign(window, { Icon, Logo, Reveal, TopNav, Footer, Stat, SITE_LINKS, siteNavGo, siteNavActive });