// components.jsx — Icons, nav, footer, cart drawer, shared UI
// Components are exported to window so sections.jsx can use them.
/* =========================================================
Icons — minimal stroke set
========================================================= */
const Ic = {
search: (p) => (
),
user: (p) => (
),
bag: (p) => (
),
heart: (p) => (
),
heartFill: (p) => (
),
arrow: (p) => (
),
star: (p) => (
),
plus: (p) => (
),
minus: (p) => (
),
close: (p) => (
),
slider: (p) => (
),
/* Benefit icons */
collagen: (p) => (
),
hydration: (p) => (
),
shield: (p) => (
),
clock: (p) => (
),
};
/* =========================================================
Brand mark — simplified V + helix
========================================================= */
function BrandMark({ size = 32, dark = false }) {
const stroke = dark ? '#0E3D2E' : '#C9A47A';
const v = dark ? '#0E3D2E' : '#0E3D2E';
return (
);
}
/* =========================================================
Authenticity seal — used in hero
========================================================= */
function AuthSeal() {
return (
);
}
/* =========================================================
Vial visualisation — drawn in SVG, color-coded by product
========================================================= */
function Vial({ accent = '#7A5BC7', label = 'NAD+', strength = '500mg', angle = 0, scale = 1 }) {
// accent = cap color
return (
);
}
/* =========================================================
Nav — showcase only (no cart / search / account)
========================================================= */
function Nav() {
const [scrolled, setScrolled] = React.useState(false);
const [menuOpen, setMenuOpen] = React.useState(false);
React.useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 24);
window.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
React.useEffect(() => {
document.body.style.overflow = menuOpen ? 'hidden' : '';
return () => {
document.body.style.overflow = '';
};
}, [menuOpen]);
const closeMenu = () => setMenuOpen(false);
return (
<>
>
);
}
// (Cart drawer removed — showcase only.)
/* =========================================================
NavWithLogo — drop-in replacement for that uses an
image logo (uploads/logo.png) in place of the BrandMark
+ "VELORA" wordmark. Same nav-links, same mobile menu,
same scroll/burger behaviour.
To enable when the client provides the final logo:
1. In app.jsx, comment out and put
in its place.
2. (Optional) Adjust .nav-logo-img height in styles.css if
you want the logo larger / smaller.
========================================================= */
function NavWithLogo() {
const [scrolled, setScrolled] = React.useState(false);
const [menuOpen, setMenuOpen] = React.useState(false);
React.useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 24);
window.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
React.useEffect(() => {
document.body.style.overflow = menuOpen ? 'hidden' : '';
return () => {
document.body.style.overflow = '';
};
}, [menuOpen]);
const closeMenu = () => setMenuOpen(false);
return (
<>
>
);
}
/* =========================================================
Footer
========================================================= */
function Footer() {
return (
);
}
/* =========================================================
Reveal — CSS-animation wrapper (always visible, fades in)
========================================================= */
function Reveal({ children, delay = 0, as: As = 'div', ...rest }) {
const cn = `reveal ${delay ? `reveal-delay-${delay}` : ''} ${rest.className || ''}`;
return (
{children}
);
}
/* =========================================================
MessengerWidget — Floating chat button (lower-right)
Uses m.me deep link. Replace MSGR_HANDLE with the real
Facebook Page username when ready.
========================================================= */
const MSGR_HANDLE = 'veloramainexclusive';
const MSGR_ID = '61590148993303';
const MSGR_WEB_URL = `https://www.facebook.com/messages/t/${MSGR_ID}`;
const MSGR_APP_URL = `fb-messenger://user-thread/${MSGR_ID}`;
// On mobile (iOS/Android), try to launch the Messenger app directly.
// Fall back to the web URL if the app doesn't open. On desktop, the
// default behavior handles opening in a new tab.
function openMessengerChat(e) {
const ua = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
const isIOS = /iPad|iPhone|iPod/.test(ua) && !window.MSStream;
const isAndroid = /Android/.test(ua);
if (!isIOS && !isAndroid) return;
if (e && e.preventDefault) e.preventDefault();
let appOpened = false;
const onVisChange = () => {
if (document.hidden) appOpened = true;
};
document.addEventListener('visibilitychange', onVisChange);
window.location.href = MSGR_APP_URL;
setTimeout(() => {
document.removeEventListener('visibilitychange', onVisChange);
if (!appOpened && !document.hidden) {
window.location.href = MSGR_WEB_URL;
}
}, 1500);
}
function MessengerLogo({ size = 28 }) {
return (
);
}
function MessengerWidget() {
const [open, setOpen] = React.useState(false);
const [seen, setSeen] = React.useState(false);
const msgrUrl = `https://www.facebook.com/messages/t/${MSGR_ID}`;
React.useEffect(() => {
if (open) setSeen(true);
}, [open]);
return (
Velora Concierge
Online · replies within 12h
Hi! Welcome to Velora BioPeptide Sciences. Tap below to continue this chat on Messenger — we'll help
you choose the right protocol.
{/*
Continue on Messenger
*/}
Continue on Messenger
Powered by Facebook Messenger
);
}
/* =========================================================
ScrollTopButton — Floating "back to top" (lower-right,
stacked above the Messenger FAB). Appears only after the
user has scrolled past the threshold.
========================================================= */
function ScrollTopButton() {
const [visible, setVisible] = React.useState(false);
React.useEffect(() => {
const THRESHOLD = 400;
const onScroll = () => setVisible(window.scrollY > THRESHOLD);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
const toTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
);
}
Object.assign(window, {
Ic,
BrandMark,
AuthSeal,
Vial,
Nav,
NavWithLogo,
Footer,
Reveal,
MessengerWidget,
ScrollTopButton,
});