/* GoldenPharm site — shared components & data (exported to window). */
/* ---------- Brand data (real distributed brands) ---------- */
const GP_BRANDS = [
{
id: 'rejuvea',
name: 'Rejuvea HA',
mark: 'R',
logo: 'assets/brand-rejuvea-ha.png',
logoRatio: 1.5,
origin: 'Europe',
category: 'HA & PDRN skincare',
tagline: 'Premium dermocosmetics',
blurb: 'Multi-molecular hyaluronic acid paired with PDRN for clinically validated, visibly regenerated skin.',
url: 'https://rejuveaha.com',
icon: 'droplet',
products: ['Multi-molecular HA serum', 'PDRN regenerating ampoules', 'Barrier-repair moisturiser'],
},
{
id: 'dermasecret',
name: 'Derma Secret',
mark: 'D',
logo: 'assets/brand-derma-secret.png',
logoRatio: 1.75,
origin: 'Italy',
category: 'Dermal fillers & mesotherapy',
tagline: 'Italian aesthetic solutions',
blurb: 'Face, lip and body fillers, mesotherapy and peels — manufactured to ISO 13485 and ISO 22716 standards.',
url: 'https://dermasecret.it',
icon: 'syringe',
products: ['Face filler', 'Lip filler', 'Body filler', 'Mesotherapy', 'Peeling'],
},
{
id: 'luminad',
name: 'LumiNAD',
mark: 'L',
logo: 'assets/brand-luminad.png',
logoRatio: 3.73,
origin: 'Europe',
category: 'NAD+ skin booster',
tagline: 'Cellular rejuvenation',
blurb: 'A NAD+ enriched 3 ml prefilled syringe skin booster for cellular rejuvenation of face, neck and décolleté.',
url: 'https://luminad.me',
icon: 'sparkles',
products: ['NAD+ skin booster (3 ml)'],
},
];
window.GP_BRANDS = GP_BRANDS;
/* ---------- Per-brand detail content (Learn more pages) ---------- */
const GP_BRAND_DETAIL = {
rejuvea: {
intro: 'Rejuvea HA is a premium dermocosmetic range built on multi-molecular hyaluronic acid and PDRN. Each formulation is designed to work at several depths of the skin at once — hydrating the surface while supporting regeneration below it — for results that are visible and clinically backed.',
highlights: [
['droplet', 'Multi-molecular HA', 'High, medium and low molecular-weight hyaluronic acid hydrate and plump across multiple skin depths.'],
['sparkles', 'PDRN regeneration', 'Polydeoxyribonucleotide supports the skin’s natural repair processes for renewed tone and texture.'],
['shield', 'Clinically validated', 'Formulations backed by clinical evaluation and manufactured to European quality standards.'],
],
facts: [['Origin', 'Europe'], ['Category', 'HA & PDRN skincare'], ['Range', '3 core formulations'], ['Best for', 'Post-procedure & daily care']],
products: [
['droplet', 'Multi-molecular HA serum', 'Layered hyaluronic-acid weights deliver multi-depth hydration and immediate plumping.'],
['sparkles', 'PDRN regenerating ampoules', 'Concentrated ampoules that support tissue repair and visible skin regeneration.'],
['heart', 'Barrier-repair moisturiser', 'Restores and reinforces the skin barrier, sealing in moisture after treatment.'],
],
certs: ['ISO 22716', 'CE marked'],
},
dermasecret: {
intro: 'Derma Secret is an Italian aesthetic house producing dermal fillers, mesotherapy and peels for face, lips and body. Every product is manufactured to ISO 13485 and ISO 22716 standards, giving practitioners a complete, compliant toolkit for corrective and enhancement work.',
highlights: [
['syringe', 'Full filler system', 'Face, lip and body fillers formulated for natural volume, contour and definition.'],
['flask', 'Mesotherapy & peels', 'Targeted mesotherapy cocktails and professional peels for skin quality and revitalisation.'],
['award', 'ISO 13485 & 22716', 'Medical-device and cosmetic GMP certification across the entire range.'],
],
facts: [['Origin', 'Italy'], ['Category', 'Fillers & mesotherapy'], ['Range', '5 product families'], ['Standards', 'ISO 13485 · 22716']],
products: [
['syringe', 'Face filler', 'Cross-linked HA fillers for volume restoration and facial contouring.'],
['droplet', 'Lip filler', 'Formulated for smooth, natural lip definition and hydration.'],
['package', 'Body filler', 'Higher-volume fillers for body contouring and correction.'],
['flask', 'Mesotherapy', 'Micro-injectable cocktails that revitalise and improve skin quality.'],
['sparkles', 'Peeling', 'Professional chemical peels for renewal, tone and texture.'],
],
certs: ['ISO 13485', 'ISO 22716', 'CE marked'],
},
luminad: {
intro: 'LumiNAD is a NAD+ enriched skin booster supplied as a 3 ml prefilled syringe. It delivers cellular rejuvenation to the face, neck and décolleté — supporting energy metabolism in skin cells for a rested, revitalised appearance.',
highlights: [
['sparkles', 'NAD+ enriched', 'Nicotinamide adenine dinucleotide supports cellular energy and repair within the skin.'],
['syringe', 'Prefilled 3 ml syringe', 'A ready-to-use sterile format for precise, convenient application.'],
['heart', 'Face, neck & décolleté', 'Formulated for the areas that show ageing first, for all-over rejuvenation.'],
],
facts: [['Origin', 'Europe'], ['Category', 'NAD+ skin booster'], ['Format', '3 ml prefilled syringe'], ['Areas', 'Face · neck · décolleté']],
products: [
['sparkles', 'NAD+ skin booster (3 ml)', 'A prefilled-syringe skin booster enriched with NAD+ for cellular rejuvenation of face, neck and décolleté.'],
],
certs: ['CE marked'],
},
};
window.GP_BRAND_DETAIL = GP_BRAND_DETAIL;
window.getBrandBySlug = function (slug) {
const b = GP_BRANDS.find((x) => x.id === slug) || GP_BRANDS[0];
return { brand: b, detail: GP_BRAND_DETAIL[b.id] };
};
/* ---------- Eyebrow ---------- */
function Eyebrow({ children, onDark, style }) {
return (
{window.GPIcons.sparkle()}
{children}
);
}
/* ---------- Pill button ---------- */
function PillButton({ children, variant = 'primary', size = 'md', iconLeft, iconRight, href, onClick, style = {} }) {
const [hover, setHover] = React.useState(false);
const sizes = {
sm: { pad: '9px 16px', fs: 14, h: 38, gap: 8, ic: 26 },
md: { pad: '12px 20px', fs: 15, h: 48, gap: 10, ic: 30 },
lg: { pad: '15px 24px', fs: 16, h: 56, gap: 12, ic: 34 },
};
const s = sizes[size] || sizes.md;
const palettes = {
primary: { bg: 'var(--accent)', bgH: 'var(--gp-blue-700)', color: '#fff', border: 'transparent', chip: 'rgba(255,255,255,0.22)', chipColor: '#fff' },
light: { bg: '#fff', bgH: 'var(--accent-soft)', color: 'var(--gp-navy-800)', border: 'var(--border-default)', chip: 'var(--accent-soft)', chipColor: 'var(--accent)' },
dark: { bg: 'var(--gp-navy-900)', bgH: 'var(--gp-navy-800)', color: '#fff', border: 'transparent', chip: 'rgba(255,255,255,0.16)', chipColor: '#fff' },
onDark: { bg: '#fff', bgH: 'var(--gp-blue-50)', color: 'var(--gp-navy-900)', border: 'transparent', chip: 'var(--accent)', chipColor: '#fff' },
ghostDark: { bg: 'transparent', bgH: 'rgba(255,255,255,0.08)', color: '#fff', border: 'rgba(255,255,255,0.32)', chip: 'rgba(255,255,255,0.16)', chipColor: '#fff' },
};
const p = palettes[variant] || palettes.primary;
const Tag = href ? 'a' : 'button';
return (
setHover(true)} onMouseLeave={() => setHover(false)}
style={{
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: s.gap,
fontFamily: 'var(--gp-font-sans)', fontSize: s.fs, fontWeight: 600, lineHeight: 1,
padding: iconLeft ? `${(s.h - s.ic) / 2}px 22px ${(s.h - s.ic) / 2}px ${(s.h - s.ic) / 2}px` : s.pad,
minHeight: s.h, borderRadius: 'var(--btn-radius)',
border: `1px solid ${hover && p.border !== 'transparent' ? 'var(--accent-light)' : p.border}`,
background: hover ? p.bgH : p.bg, color: p.color,
cursor: 'pointer', textDecoration: 'none', whiteSpace: 'nowrap',
boxShadow: variant === 'primary' && hover ? 'var(--shadow-3)' : 'none',
transition: 'background var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard), border-color var(--dur-fast)',
...style,
}}>
{iconLeft && (
{iconLeft}
)}
{children}
{iconRight && {iconRight}}
);
}
/* ---------- Circle arrow button (card corner action) ---------- */
function CircleArrow({ tone = 'dark', size = 44, href, onClick, icon, style = {} }) {
const [hover, setHover] = React.useState(false);
const tones = {
dark: { bg: 'var(--gp-navy-900)', bgH: 'var(--accent)', color: '#fff' },
blue: { bg: 'var(--accent)', bgH: 'var(--gp-blue-700)', color: '#fff' },
light: { bg: '#fff', bgH: 'var(--accent-soft)', color: 'var(--accent)' },
};
const t = tones[tone] || tones.dark;
const Tag = href ? 'a' : 'button';
return (
setHover(true)} onMouseLeave={() => setHover(false)}
style={{
width: size, height: size, borderRadius: '50%', flex: '0 0 auto',
border: t.bg === '#fff' ? '1px solid var(--border-default)' : 'none',
background: hover ? t.bgH : t.bg, color: t.color,
display: 'grid', placeItems: 'center', cursor: 'pointer', textDecoration: 'none',
transition: 'background var(--dur-base) var(--ease-standard), transform var(--dur-base)',
transform: hover ? 'rotate(45deg)' : 'none', ...style,
}}>
{icon || window.GPIcons.arrowUpRight()}
);
}
/* ---------- Placeholder imagery ---------- */
function Placeholder({ label = 'Photography', icon = 'sparkle', dark, radius = 'var(--radius-2xl)', className = '', style = {} }) {
return (
{window.GPIcons[icon] ? window.GPIcons[icon]() : window.GPIcons.sparkle()}
{label}
);
}
/* ---------- User-fillable image slot (drag a real photo in; it persists) ---------- */
function ImageSlot({ id, label = 'Drop an image', radius = 24, src, className = '', style = {} }) {
const rect = Number(radius) === 0;
return React.createElement('image-slot', {
id,
placeholder: label,
shape: rect ? 'rect' : 'rounded',
radius: String(rect ? 0 : radius),
src: src || undefined,
className: `gp-slot ${className}`.trim(),
style: { display: 'block', width: '100%', height: '100%', ...style },
});
}
/* ---------- Floating chip ---------- */
function FloatingChip({ icon, children, style = {} }) {
return (
{icon &&
{icon}
}
{children}
);
}
/* ---------- Brand wordmark ---------- */
function BrandMark({ brand, size = 'md' }) {
const s = size === 'lg' ? { box: 48, fs: 22, name: 22, logoH: 58 } : size === 'sm' ? { box: 32, fs: 15, name: 15, logoH: 34 } : { box: 40, fs: 19, name: 18, logoH: 44 };
// Real brand logo lockup (wordmark already contains the name)
if (brand.logo) {
return (
);
}
return (
{brand.mark}
{brand.name}
);
}
/* ---------- Reveal on scroll (position-based + self-clearing poll — robust everywhere) ---------- */
function useReveal() {
React.useEffect(() => {
let poll = 0;
const check = () => {
const vh = window.innerHeight || document.documentElement.clientHeight;
const pending = document.querySelectorAll('.reveal:not(.in)');
pending.forEach((el) => {
if (el.getBoundingClientRect().top < vh * 0.9) el.classList.add('in');
});
// once everything has revealed, stop polling
if (poll && document.querySelectorAll('.reveal:not(.in)').length === 0) {
clearInterval(poll); poll = 0;
}
};
check();
// poll catches programmatic scrolls (which don't always emit scroll events in embedded iframes)
poll = setInterval(check, 220);
window.addEventListener('scroll', check, { passive: true });
window.addEventListener('resize', check);
return () => {
window.removeEventListener('scroll', check);
window.removeEventListener('resize', check);
if (poll) clearInterval(poll);
};
}, []);
}
/* ---------- Scroll progress bar ---------- */
function ScrollProgress() {
const [p, setP] = React.useState(0);
React.useEffect(() => {
const on = () => {
const h = document.documentElement;
const max = h.scrollHeight - h.clientHeight;
setP(max > 0 ? Math.min(1, h.scrollTop / max) : 0);
};
on();
window.addEventListener('scroll', on, { passive: true });
window.addEventListener('resize', on);
return () => { window.removeEventListener('scroll', on); window.removeEventListener('resize', on); };
}, []);
return ;
}
/* ---------- Count-up number (animates once on scroll into view) ---------- */
function CountUp({ value, duration = 1500 }) {
const ref = React.useRef(null);
const [disp, setDisp] = React.useState(String(value));
React.useEffect(() => {
const m = String(value).match(/^([^\d-]*)(-?[\d.,]+)(.*)$/);
if (!m) { setDisp(String(value)); return; }
const prefix = m[1], numStr = m[2], suffix = m[3];
const decimals = numStr.includes('.') ? (numStr.split('.')[1] || '').length : 0;
const target = parseFloat(numStr.replace(/,/g, ''));
const fmt = (n) => prefix + (decimals ? n.toFixed(decimals) : Math.round(n).toLocaleString()) + suffix;
const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) { setDisp(fmt(target)); return; }
setDisp(fmt(0));
let raf = 0, done = false;
const run = () => {
const t0 = performance.now();
const tick = (now) => {
const k = Math.min(1, (now - t0) / duration);
setDisp(fmt(target * (1 - Math.pow(1 - k, 3))));
if (k < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
};
const io = new IntersectionObserver((es) => {
es.forEach((e) => { if (e.isIntersecting && !done) { done = true; run(); io.disconnect(); } });
}, { threshold: 0.4 });
if (ref.current) io.observe(ref.current);
return () => { cancelAnimationFrame(raf); io.disconnect(); };
}, [value, duration]);
return {disp};
}
/* ---------- Animated stat block (on dark bands) ---------- */
function AnimatedStat({ value, label }) {
return (
);
}
/* ---------- Back-to-top button ---------- */
function BackToTop() {
const [show, setShow] = React.useState(false);
React.useEffect(() => {
const on = () => setShow(window.scrollY > 620);
on();
window.addEventListener('scroll', on, { passive: true });
return () => window.removeEventListener('scroll', on);
}, []);
return ReactDOM.createPortal(
, document.body);
}
/* ---------- Mobile menu (full-screen, animated, premium) ---------- */
function MobileMenu({ open, onClose, links, current }) {
const [render, setRender] = React.useState(false);
const [closing, setClosing] = React.useState(false);
React.useEffect(() => {
if (open) {
setRender(true);
setClosing(false);
} else if (render) {
setClosing(true);
const t = setTimeout(() => { setRender(false); setClosing(false); }, 320);
return () => clearTimeout(t);
}
}, [open]); // eslint-disable-line
// lock body scroll + close on Escape while open
React.useEffect(() => {
if (!render) return;
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
return () => { document.body.style.overflow = prev; window.removeEventListener('keydown', onKey); };
}, [render, onClose]);
if (!render) return null;
const shown = !closing;
return ReactDOM.createPortal((
), document.body);
}
/* ---------- Brands dropdown (desktop nav) ---------- */
function NavBrandsDropdown({ link, current }) {
const [open, setOpen] = React.useState(false);
const closeT = React.useRef(null);
const brands = window.GP_BRANDS || [];
const openNow = () => { clearTimeout(closeT.current); setOpen(true); };
const closeSoon = () => { closeT.current = setTimeout(() => setOpen(false), 130); };
return (
);
}
/* ---------- Navigation ---------- */
function Nav({ current = 'home' }) {
const [open, setOpen] = React.useState(false);
const [scrolled, setScrolled] = React.useState(false);
React.useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 8);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
const links = [
{ id: 'home', label: 'Home', href: 'index.html' },
{ id: 'brands', label: 'Brands', href: 'brands.html' },
{ id: 'about', label: 'About us', href: 'about.html' },
{ id: 'contact', label: 'Contact us', href: 'contact.html' },
];
return (
setOpen(false)} links={links} current={current} />
);
}
/* ---------- Footer ---------- */
function Footer() {
const cols = [
{ title: 'Company', links: [['About us', 'about.html'], ['Brands', 'brands.html'], ['Quality & compliance', 'about.html'], ['Careers', 'contact.html']] },
{ title: 'Brands', links: [['All brands', 'brands.html'], ['Rejuvea HA', 'brands.html'], ['Derma Secret', 'brands.html'], ['LumiNAD', 'brands.html']] },
];
return (
);
}
Object.assign(window, { Eyebrow, PillButton, CircleArrow, Placeholder, ImageSlot, FloatingChip, BrandMark, useReveal, Nav, Footer, ScrollProgress, CountUp, AnimatedStat, BackToTop });