// Shared UI: Nav, Footer, Logo, Reveal, Icons
const { useState, useEffect, useRef, useMemo } = React;
// ============ LOGO ============
function LogoMark({ color = '#1A2B3C', dot = '#00C9A7', size = 32 }) {
return (
);
}
function LogoWord({ variant = 'dark' }) {
const stroke = variant === 'light' ? '#FFFFFF' : '#1A2B3C';
const fill = variant === 'light' ? '#FFFFFF' : '#1A2B3C';
return (
);
}
// ============ ICONS ============
const Icon = {
arrow: (p) => ,
chevron: (p) => ,
send: (p) => ,
check: (p) => ,
x: (p) => ,
plus: (p) => ,
minus: (p) => ,
sparkle: (p) => ,
map: (p) => ,
euro: (p) => ,
bed: (p) => ,
area: (p) => ,
globe: (p) => ,
};
// ============ Reveal (scroll-in) ============
function Reveal({ children, delay = 0, as: As = 'div', className = '', style = {} }) {
const ref = useRef(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (!ref.current) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) { setVisible(true); obs.disconnect(); }
});
}, { threshold: 0.12 });
obs.observe(ref.current);
return () => obs.disconnect();
}, []);
return (
{children}
);
}
// ============ NAV ============
function Nav({ t, lang, setLang, page, setPage, onStartChat }) {
const [scrolled, setScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 8);
window.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
const links = [
{ id: 'how', label: t.nav.how },
{ id: 'pricing', label: t.nav.pricing },
{ id: 'investors', label: t.nav.investors },
{ id: 'about', label: t.nav.about },
{ id: 'blog', label: t.nav.blog },
{ id: 'contact', label: t.nav.contact },
{ id: 'agent', label: t.nav.agentTool, highlight: true },
];
return (
{ e.preventDefault(); setPage('home'); }} href="#" aria-label="Keynara" style={{ display: 'flex', alignItems: 'center' }}>
{mobileOpen && (
)}
);
}
function LangToggle({ lang, setLang }) {
return (
{['de', 'en'].map(l => (
))}
);
}
// ============ FOOTER ============
function Footer({ t, setPage }) {
const cols = [
{ title: t.footer.cols.product, links: [
{ k: 'how', label: t.footer.links.how },
{ k: 'pricing', label: t.footer.links.pricing },
{ k: 'investors', label: t.footer.links.investors },
{ k: 'contact', label: t.footer.links.api },
]},
{ title: t.footer.cols.company, links: [
{ k: 'about', label: t.footer.links.about },
{ k: 'about', label: t.footer.links.careers },
{ k: 'blog', label: t.footer.links.press },
{ k: 'contact', label: t.footer.links.contact },
]},
{ title: t.footer.cols.resources, links: [
{ k: 'blog', label: t.footer.links.blog },
{ k: 'contact', label: t.footer.links.help },
{ k: 'contact', label: t.footer.links.status },
{ k: 'about', label: t.footer.links.security },
]},
{ title: t.footer.cols.legal, links: [
{ k: 'contact', label: t.footer.links.imprint },
{ k: 'contact', label: t.footer.links.privacy },
{ k: 'contact', label: t.footer.links.terms },
{ k: 'contact', label: t.footer.links.cookies },
]},
];
return (
);
}
function NewsletterBlock({ t }) {
const [email, setEmail] = useState('');
const [sent, setSent] = useState(false);
return (
{t.newsletter.title}
{t.newsletter.sub}
);
}
Object.assign(window, { LogoMark, LogoWord, Icon, Reveal, Nav, Footer });