/* global React */
const { useState, useEffect, useRef, useMemo, useLayoutEffect } = React;
// ───────────────────────── Icons ─────────────────────────
const Icon = {
Lock: ({ size = 14 }) => (
),
Arrow: ({ size = 14 }) => (
),
ArrowLeft: ({ size = 14 }) => (
),
Check: ({ size = 14 }) => (
),
Plus: ({ size = 14 }) => (
),
Calendar: ({ size = 16 }) => (
),
Menu: ({ size = 18 }) => (
),
Close: ({ size = 18 }) => (
),
Upload: ({ size = 14 }) => (
),
Caret: ({ size = 9, open = false }) => (
),
ExtLink: ({ size = 12 }) => (
),
Dot: () => ,
Search: ({ size = 14 }) => (
),
Filter: ({ size = 14 }) => (
),
};
// ───────────────────────── Wordmark ─────────────────────────
const Wordmark = ({ size = 22, color, capex = false }) => (
Buy
side
{capex && FOR BUYERS }
);
// ───────────────────────── Eyebrow / Section header ─────────────────────────
const Eyebrow = ({ children, dot = true, color }) => (
{dot && }{children}
);
// ───────────────────────── Lock pill ─────────────────────────
const LockPill = ({ children = "100% confidential — no listing, no public exposure" }) => (
{children}
);
// ───────────────────────── Mobile status bar ─────────────────────────
const MobileStatusBar = () => (
);
// ───────────────────────── Intent badge ─────────────────────────
const INTENT_META = {
actively: { cls: 'intent-actively', label: 'Actively exploring' },
soon: { cls: 'intent-soon', label: 'Planning 6–12 mo' },
later: { cls: 'intent-later', label: 'Planning 1–2 yr' },
curious: { cls: 'intent-curious', label: 'Just curious' },
};
const IntentBadge = ({ kind = 'curious' }) => {
const m = INTENT_META[kind] || INTENT_META.curious;
return {m.label} ;
};
// ───────────────────────── Lock tile (anonymous buyer card icon) ─────────────────────────
const LockTile = ({ initials, size = 40 }) => (
{initials ? (
{initials}
) : (
)}
);
// ───────────────────────── Trust strip ─────────────────────────
const TrustStrip = ({ inverse = false }) => (
2,400+ owners matched
·
$4.2B in active buyer mandates
·
SOC 2 compliant
);
// ───────────────────────── Footer ─────────────────────────
const Footer = ({ variant = 'seller', navigate }) => (
A confidential matching service.
{variant === 'seller' ? ' A confidential alternative to listing your business.' : ' For verified institutional buyers.'}
© 2025 Buyside, Inc.
SOC 2 Type II · CCPA · GDPR
);
// ───────────────────────── NavMenu (dropdown) ─────────────────────────
const NavMenu = ({ items, navigate, onPick }) => (
{/* little tick mark up top */}
{items.map((it, i) => (
{ navigate(it.id); onPick && onPick(); }}
style={{
display: 'block', width: '100%', textAlign: 'left',
padding: '12px 14px', background: 'none', border: 'none', cursor: 'pointer',
fontFamily: 'inherit', borderBottom: i < items.length - 1 ? '1px solid var(--rule)' : 'none',
}}
onMouseEnter={(e) => { e.currentTarget.style.background = '#efece4'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
>
{it.label}
{it.sub && {it.sub}
}
))}
);
// ───────────────────────── Mobile Nav Drawer ─────────────────────────
const MobileNavDrawer = ({ open, onClose, navigate, currentScreen }) => {
if (!open) return null;
const go = (id) => { navigate(id); onClose(); };
return (
e.stopPropagation()} style={{ position: 'absolute', top: 0, right: 0, bottom: 0, width: 'min(86vw, 340px)', background: 'var(--paper)', boxShadow: '-10px 0 40px rgba(0,0,0,0.15)', display: 'flex', flexDirection: 'column', overflow: 'auto', animation: 'slideInRight 280ms cubic-bezier(0.2,0.8,0.2,1)' }}>
{[{id:'seller-landing',label:'Home'},{id:'who-buys',label:'Who buys'}].map(it => (
go(it.id)} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '14px 24px', fontSize: 16, fontFamily: 'var(--serif)', fontWeight: currentScreen === it.id ? 500 : 400, color: 'var(--ink)', background: currentScreen === it.id ? '#efece4' : 'none', border: 'none', cursor: 'pointer', borderBottom: '1px solid var(--rule)' }}>{it.label}
))}
Insights
{[{id:'insights-reports',label:'Market Reports'},{id:'insights-multiples',label:'Industry Multiples'},{id:'insights-cases',label:'Case Studies'},{id:'insights-glossary',label:'Glossary'}].map(it => (
go(it.id)} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '12px 24px', fontSize: 14, color: 'var(--ink-2)', fontWeight: currentScreen === it.id ? 500 : 400, background: currentScreen === it.id ? '#efece4' : 'none', border: 'none', cursor: 'pointer' }}>{it.label}
))}
About
{[{id:'about-story',label:'Our Story'},{id:'about-team',label:'Team'},{id:'about-contact',label:'Contact'}].map(it => (
go(it.id)} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '12px 24px', fontSize: 14, color: 'var(--ink-2)', fontWeight: currentScreen === it.id ? 500 : 400, background: currentScreen === it.id ? '#efece4' : 'none', border: 'none', cursor: 'pointer' }}>{it.label}
))}
go('buyer-landing')} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '8px 0', fontSize: 13, color: 'var(--ink-4)', background: 'none', border: 'none', cursor: 'pointer' }}>For buyers →
go('seller-wizard')}>See your matches
Takes about 90 seconds
);
};
// Hamburger button — used in any compact header on mobile
const MobileHamburger = ({ onClick }) => (
);
// ───────────────────────── Phone formatter ─────────────────────────
const formatPhone = (value) => {
const digits = value.replace(/\D/g, '').slice(0, 10);
if (digits.length <= 3) return digits;
if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
};
// ───────────────────────── Submit + Generate ─────────────────────────
const submitAndGenerate = async (answers) => {
const payload = {
industry: answers.industry,
revenue: answers.revenue,
ebitda: answers.ebitda,
years_owned: answers.years_owned,
state: answers.state,
transition: answers.transition,
intent: answers.intent,
email: answers.email,
phone: answers.phone || '',
};
// Fire both requests in parallel
const [submitRes, generateRes] = await Promise.all([
fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}),
fetch('/api/generate-results', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}),
]);
if (!generateRes.ok) throw new Error('Failed to generate results');
const results = await generateRes.json();
return results;
};
// Export to window
Object.assign(window, {
Icon, Wordmark, Eyebrow, LockPill, MobileStatusBar,
IntentBadge, INTENT_META, LockTile, TrustStrip, Footer, NavMenu,
MobileNavDrawer, MobileHamburger, formatPhone, submitAndGenerate,
});