// components.jsx, shared building blocks (NL)
const { useState, useEffect, useRef, useMemo } = React;
// ─────────────────────────────────────────────────────────────────────────────
// Icons (inline SVG, small + clean)
// ─────────────────────────────────────────────────────────────────────────────
const Icon = {
Menu: () =>
,
Search: () =>
,
User: () =>
,
Bag: () =>
,
ArrowR: () =>
,
ArrowL: () =>
,
X: () =>
,
Star: ({ filled = true, half = false }) =>
,
Check: () =>
,
Plus: () => ,
Minus: () =>
};
// ─────────────────────────────────────────────────────────────────────────────
// Data
// ─────────────────────────────────────────────────────────────────────────────
const PRODUCTS = [
{
id: 'lite-300', name: 'LITE 300', sub: 'Desktop Rood Licht Paneel',
cat: 'panel', tag: 'Bestseller', price: 279, was: 399,
rating: 4.8, reviews: 220,
desc: 'Een compact desktoppaneel voor gezicht, hoofdhuid en gerichte herstelsessies. 660nm rood + 850nm nabij-infrarood in één armatuur.',
badges: ['Gericht', 'Beste prijs'],
images: ['img/lite300-2.png', 'img/lite300-1.png', 'img/lite300-3.png', 'img/lite300-4.png']
},
{
id: 'lite-1500', name: 'LITE 1500', sub: 'Half-Body Rood Licht Paneel',
cat: 'panel', tag: 'Sale', price: 1044, was: 1499,
rating: 4.9, reviews: 383,
desc: 'Half-body bedekking voor huid, herstel en circadiaanse ondersteuning. Dagelijkse sessies in minder dan 15 minuten.',
badges: ['Half-body', 'Premium'],
images: ['img/lite1500-2.png', 'img/lite1500-1.png', 'img/lite1500-stand.jpg', 'img/lite1500-3.png']
},
{
id: 'core-300', name: 'CORE 300', sub: 'Gericht Rood Licht Paneel',
cat: 'panel', tag: 'Premium', price: 349, was: 499,
rating: 4.9, reviews: 142,
desc: 'Hogere irradiantie voor gerichte behandelingen. Puls- en flikkervrije werking.',
badges: ['Gericht', 'Premium'],
images: ['img/core300-2.png', 'img/core300-1.png', 'img/core300-3.png', 'img/core300-4.png']
},
{
id: 'core-1500', name: 'CORE 1500', sub: 'Full-Body Rood Licht Paneel',
cat: 'panel', tag: 'Premium', price: 1179, was: 1699,
rating: 4.9, reviews: 96,
desc: 'Klinische full-body irradiantie met premium afwerking. 4 golflengtes, lage EMF, modulaire bevestiging.',
badges: ['Full-body', 'Premium'],
images: ['img/duo-labels.png', 'img/hero-duo-lifestyle.png', 'img/lite1500-2.png', 'img/duo-labels-2.png']
},
{
id: 'core-duo', name: 'CORE 1500 DUO', sub: 'Full-Body Combi Set',
cat: 'bundle', tag: 'Combi', price: 2199, was: 3797,
rating: 5.0, reviews: 44,
desc: 'Twee CORE 1500 panelen voor volledige lichaamsbedekking. Inclusief muurmontage.',
badges: ['Bundel', 'Beste deal'],
images: ['img/duo-labels.png', 'img/hero-duo-lifestyle.png', 'img/hero-duo-lifestyle-2.png', 'img/duo-labels-2.png']
},
{
id: 'aura', name: 'AURA+', sub: 'LED Gezichts- & Décolletémasker',
cat: 'mask', tag: 'Nieuw', price: 344.95, was: 599.90,
rating: 4.8, reviews: 312,
desc: 'Flexibel siliconen LED-masker voor gezicht en décolleté. 4 golflengtes, handsfree, sessies van 10 minuten.',
badges: ['Gezicht', 'Premium'],
images: ['img/aura-product.png', 'img/aura-lifestyle.png', 'img/aura-product-2.png', 'img/aura-lifestyle-2.png']
},
{
id: 'blanket', name: 'Infrarood Sauna Deken', sub: 'Detox & Herstel',
cat: 'sauna', tag: 'Beste deal', price: 399, was: 499,
rating: 4.7, reviews: 261,
desc: 'Infrarooddeken met lage EMF en niet-toxische binnenkant. Warmt op tot 75°C in minder dan 15 minuten.',
badges: ['Herstel', 'Detox'],
images: ['img/blanket-1.png', 'img/blanket-2.png', 'img/blanket-3.png', 'img/blanket-4.png']
},
{
id: 'lite-bed', name: 'LITE Bed', sub: 'Full-Body Therapiebed',
cat: 'bed', tag: 'Pro', price: 6499, was: 7999,
rating: 4.9, reviews: 18,
desc: 'Horizontaal therapiebed voor het hele lichaam. Voor klinieken, studio’s en serieuze thuissetups.',
badges: ['Full-body', 'Pro'],
images: ['img/lite1500-stand.jpg', 'img/lite1500-stand-alt.jpg', 'img/lite1500-2.png', 'img/hero-duo-lifestyle.png']
}];
const CATEGORIES = [
{ id: 'all', label: 'Alles' },
{ id: 'panel', label: 'Panelen' },
{ id: 'mask', label: 'Masker' },
{ id: 'sauna', label: 'Sauna' },
{ id: 'bed', label: 'Bedden' },
{ id: 'bundle', label: 'Bundels' }];
const NAV = [
{ id: 'shop', label: 'Alle producten' },
{ id: 'panels', label: 'Panelen', cat: 'panel' },
{ id: 'mask', label: 'Masker', cat: 'mask' },
{ id: 'blanket', label: 'Deken', cat: 'sauna' },
{ id: 'beds', label: 'Bedden', cat: 'bed' },
{ id: 'best', label: 'Bestsellers' }];
const MEGA = {
shop: {
title: 'Shop',
links: ['Panelen', 'AURA+ Masker', 'Saunadeken', 'Therapiebedden', 'Accessoires', 'Alles bekijken'],
products: [
{ id: 'core-1500', label: 'CORE 1500\nFull-Body Paneel' },
{ id: 'aura', label: 'AURA+\nLED Masker' }]
},
panels: {
title: 'Rood Licht Panelen',
links: ['LITE 300', 'LITE 1500', 'CORE 300', 'CORE 1500', 'CORE 1500 DUO', 'Alle panelen'],
products: [
{ id: 'lite-1500', label: 'LITE 1500\nHalf-Body' },
{ id: 'core-1500', label: 'CORE 1500\nFull-Body' }]
},
mask: {
title: 'AURA+ Masker',
links: ['AURA+ Gezichtsmasker', 'AURA+ Décolleté', 'AURA+ Bundel', 'Accessoires'],
products: [
{ id: 'aura', label: 'AURA+\nGezichtsmasker' },
{ id: 'aura', label: 'AURA+ Décolleté\nBundel' }]
},
blanket: {
title: 'Saunadeken',
links: ['Infrarooddeken', 'Inlegvellen', 'Reistas', 'Accessoires'],
products: [
{ id: 'blanket', label: 'Infrarood\nSaunadeken' },
{ id: 'blanket', label: 'Deken\n+ Inlegvel set' }]
},
beds: {
title: 'Therapiebedden',
links: ['LITE Bed', 'CORE Bed', 'PRO Bed', 'Voor klinieken', 'Financiering'],
products: [
{ id: 'lite-bed', label: 'LITE Bed\nFull-Body' },
{ id: 'lite-bed', label: 'PRO Bed\nKlinieken' }]
},
best: {
title: 'Bestsellers',
links: ['Meest geliefd', 'Nieuw binnen', 'In de uitverkoop', 'Alles bekijken'],
products: [
{ id: 'lite-300', label: 'LITE 300\nDesktop Paneel' },
{ id: 'aura', label: 'AURA+\nLED Masker' },
{ id: 'blanket', label: 'Infrarood\nDeken' }]
}
};
const findProduct = (id) => PRODUCTS.find((p) => p.id === id) || PRODUCTS[0];
// ─────────────────────────────────────────────────────────────────────────────
// Promo bar
// ─────────────────────────────────────────────────────────────────────────────
function PromoBar() {
const items = [
'120 Dagen Risicovrij Thuis Uitproberen',
'LENTE SALE: TIJDELIJK TOT 40% KORTING',
'Gratis Verzekerde Verzending',
'★★★★★ 4,8/5 UIT 185+ RECENSIES',
'Achteraf betalen met Klarna'];
const loop = [...items, ...items, ...items];
return (
{loop.map((m, i) =>
{m} ◦
)}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Header (sticky, centered wordmark)
// ─────────────────────────────────────────────────────────────────────────────
function Header({ onNav, onOpenMega, onCloseMega, openMega, onOpenCart, cartCount, currentPage }) {
return (
{NAV.map((it) =>
openMega === it.id ? onCloseMega() : onOpenMega(it.id)}>
{it.label}
)}
{onCloseMega();onNav('home');}}>
Doe de quiz
onNav('product', 'core-1500')}>Bekijk CORE 1500
{cartCount > 0 && {cartCount} }
NL | EN
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Mega menu overlay
// ─────────────────────────────────────────────────────────────────────────────
function MegaMenu({ open, onClose, onNav }) {
const data = open ? MEGA[open] : null;
const isOpen = !!data;
useEffect(() => {
if (!isOpen) return;
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [isOpen, onClose]);
return (
<>
>);
}
// ─────────────────────────────────────────────────────────────────────────────
// Star rating bit
// ─────────────────────────────────────────────────────────────────────────────
function Stars({ rating, reviews }) {
return (
{[1, 2, 3, 4, 5].map((i) =>
)}
({reviews})
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Price formatter
// ─────────────────────────────────────────────────────────────────────────────
const fmt = (n) => '€' + n.toLocaleString('nl-NL', { minimumFractionDigits: n % 1 ? 2 : 0, maximumFractionDigits: 2 });
// ─────────────────────────────────────────────────────────────────────────────
// Product card
// ─────────────────────────────────────────────────────────────────────────────
function ProductCard({ product, onView, onAdd, slotPrefix = 'card' }) {
const [imgIdx, setImgIdx] = useState(0);
const images = product.images || [];
const total = images.length || 1;
const cycle = (delta) => setImgIdx((imgIdx + total + delta) % total);
return (
onView(product.id)}>
{product.tag &&
{product.tag} }
{images.map((src, i) =>
)}
{e.stopPropagation();cycle(-1);}} aria-label="Vorige">
{e.stopPropagation();cycle(1);}} aria-label="Volgende">
{Array.from({ length: total }).map((_, i) =>
)}
onView(product.id)}>
Nuvibody {product.name}
{product.sub}
{product.was && {fmt(product.was)} }
{fmt(product.price)}
{e.stopPropagation();onView(product.id);}} style={{ flex: 1, justifyContent: 'center', background: '#da2000', color: '#fff', borderColor: '#da2000' }}>Bekijk meer
{e.stopPropagation();onAdd(product);}} style={{ flex: 1, justifyContent: 'center', background: '#ffffff', color: 'var(--ink)', borderColor: 'var(--ink)' }}>In winkelmand
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Mini cart
// ─────────────────────────────────────────────────────────────────────────────
function MiniCart({ open, onClose, items, onChangeQty, onRemove, onCheckout }) {
const total = items.reduce((s, i) => s + i.product.price * i.qty, 0);
return (
<>
Jouw winkelmand ({items.length})
{items.length === 0 &&
Je winkelmand is leeg.
Begin met winkelen
}
{items.map((it) =>
Nuvibody {it.product.name}
{it.product.sub}
onChangeQty(it.product.id, -1)} aria-label="Min">
{it.qty}
onChangeQty(it.product.id, 1)} aria-label="Plus">
onRemove(it.product.id)} style={{ marginLeft: 8, color: 'var(--ink-3)' }}>Verwijder
{fmt(it.product.price * it.qty)}
)}
{items.length > 0 &&
Subtotaal
{fmt(total)}
Verzendkosten & btw worden berekend bij het afrekenen. Gratis verzekerde verzending vanaf €50.
Afrekenen, {fmt(total)}
{['iDEAL', 'Klarna', 'Apple Pay', 'PayPal', 'Visa'].map((p) =>
{p}
)}
}
>);
}
// ─────────────────────────────────────────────────────────────────────────────
// Toast (added to bag confirmation)
// ─────────────────────────────────────────────────────────────────────────────
function Toast({ msg, show }) {
return {msg}
;
}
// ─────────────────────────────────────────────────────────────────────────────
// Footer
// ─────────────────────────────────────────────────────────────────────────────
function Footer({ onNav }) {
return (
Klinische kwaliteit rood licht therapie. Ontworpen in Nijmegen. Gebouwd voor de lange termijn.
Product Quiz
© 2026 Nuvibody, Spoorstraat 30a, 6511AH Nijmegen · KVK 80973698
{['iDEAL', 'Klarna', 'Apple Pay', 'Bancontact', 'PayPal', 'Mastercard', 'Visa', 'Shop Pay'].map((p) => {p} )}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Trust strip (4-up)
// ─────────────────────────────────────────────────────────────────────────────
function TrustStrip() {
const items = [
{ label: '120 Dagen op Proef', sub: 'Risicovrij thuis uitproberen' },
{ label: 'Achteraf betalen', sub: 'Shop nu, betaal later met Klarna' },
{ label: 'Tot 5 Jaar Garantie', sub: 'Op uitgekozen modellen' },
{ label: 'Gratis Verzending', sub: 'Verzekerd, in 2–4 dagen' }];
return (
{items.map((it, i) =>
{it.sub}
{it.label}
)}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Press strip (marquee)
// ─────────────────────────────────────────────────────────────────────────────
function PressStrip() {
const items = [
{ logo: 'Noor, 44', quote: 'Ik gebruik het nu drie maanden en mijn huid is rustiger dan ooit. Minder roodheid, gladder.' },
{ logo: 'James, 28', quote: 'Eerste 2 weken voelde ik me moe, daarna ineens lichter, energieker. Alsof er iets "aan" ging.' },
{ logo: 'Lisette, 52', quote: 'Na jaren fibromyalgie voelde ik voor het eerst verlichting die niet uit een potje kwam. Geen wondermiddel, maar wél echt effect.' },
{ logo: 'Holly D., 34', quote: 'Wat mij verraste, was hoeveel rust het in m\u2019n hoofd bracht. Ik slaap beter, maar voel me vooral helderder overdag.' },
{ logo: 'Marijke, 49', quote: 'Ik gebruik het met mijn dochter. Zij tegen acne, ik tegen pigmentvlekken. We zien allebei resultaat.' },
{ logo: 'Petra, 48', quote: 'Sinds ik dit gebruik, heb ik overdag meer focus, \u2019s avonds meer rust. Ik voel me gewoon stabieler.' }];
const loop = [...items, ...items];
return (
);
}
// Expose to global scope
Object.assign(window, {
Icon, PRODUCTS, CATEGORIES, NAV, MEGA, findProduct, fmt,
PromoBar, Header, MegaMenu, ProductCard, MiniCart, Toast, Footer, TrustStrip, PressStrip, Stars
});