// shared.jsx — Verigo Global: brand mark, icons, navigation, footer, UI primitives
// All exported to window for cross-file use.
/* ── PALETTE ─────────────────────────────────────────────── */
const V = {
purple: '#4B4FD9', purpleLight: '#6E72E8', purpleDark: '#3438B0', purpleSubtle: '#EBEBFA',
orange: '#E8622A', orangeLight: '#F08050', orangeDark: '#C44E1E', orangeSubtle: '#FDF0EA',
black: '#0A0A0A', charcoal: '#1E1E2E', ink: '#111118',
g900: '#111118', g800: '#222230', g700: '#3A3A4A', g600: '#5A5A6A', g500: '#7A7A8A',
g400: '#9A9AAA', g300: '#C0C0CC', g200: '#DCDCE8', g100: '#EDEDF5', g50: '#F5F5F7', white: '#FFFFFF'
};
const MAXW = 1280;
const FONT = "'DM Sans', system-ui, sans-serif";
/* ── SUPABASE (form inquiries) ──────────────────────────────
Loaded via CDN script tag (window.supabase) on every page.
The anon key below is the public/browser key — it is safe to
ship client-side; write access is restricted by the `inquiries`
table's Row Level Security policy (insert-only, no read). ── */
const SUPABASE_URL = 'https://vgktunjxooukoinvxqcl.supabase.co';
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZna3R1bmp4b291a29pbnZ4cWNsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODIwODg3NzYsImV4cCI6MjA5NzY2NDc3Nn0.vyUi8XCNqiGJGqp_UtawGXFEQD7V5VCBpyAeo5RO54g';
function getSupabaseClient() {
if (!window.supabase || !window.supabase.createClient) return null;
if (!window.__vgSupabase) {
window.__vgSupabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
}
return window.__vgSupabase;
}
// Called after a successful insert. Fires the admin-notification Edge Function
// directly from the browser — no Database Webhook involved, so it isn't
// affected by that feature's setup on any given Supabase project. Never
// throws and never awaited by the caller: a slow or failed email send must
// never block or fail the form itself, since the inquiry is already saved.
function notifyAdmin(row) {
try {
fetch(SUPABASE_URL + '/functions/v1/send-inquiry-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + SUPABASE_ANON_KEY },
body: JSON.stringify({ record: { ...row, created_at: new Date().toISOString() } }),
}).catch((e) => console.warn('Admin email notification failed (inquiry was still saved):', e));
} catch (e) {
console.warn('Admin email notification failed (inquiry was still saved):', e);
}
}
// Insert into the `inquiries` table, then fire the admin notification email.
// Never throws and never blocks the calling form's existing UX (unlock/download/
// etc still happens even if this fails or the SDK hasn't loaded yet — this is
// additive, not load-bearing).
async function submitInquiry(payload) {
try {
const client = getSupabaseClient();
if (!client) { console.warn('Supabase not available; inquiry not saved remotely.'); return { error: 'no-client' }; }
const row = {
form_type: payload.form_type || 'unknown',
name: payload.name || null,
email: payload.email || null,
company: payload.company || null,
phone: payload.phone || null,
framework: payload.framework || null,
service_interest: payload.service_interest || null,
message: payload.message || null,
source_page: (typeof window !== 'undefined' && window.location) ? window.location.pathname : null,
metadata: payload.metadata || null,
};
const { error } = await client.from('inquiries').insert([row]);
if (error) { console.error('Supabase insert failed:', error.message); return { error }; }
notifyAdmin(row);
return { error: null };
} catch (e) {
console.error('submitInquiry error:', e);
return { error: e };
}
}
/* ── BRAND MARK (split-color V with orange accent) ───────── */
const BrandMark = ({ size = 40, light = false }) =>
;
/* ── ICONS — lucide-style, 24×24, 1.75 stroke ───────────── */
const I = (paths) => ({ size = 24, color = 'currentColor', sw = 1.75 } = {}) =>
;
const ICN = {
shield: I(<>>),
search: I(<>>),
clipboard: I(<>>),
file: I(<>>),
activity: I(<>>),
users: I(<>>),
globe: I(<>>),
target: I(<>>),
layers: I(<>>),
award: I(<>>),
gauge: I(<>>),
refresh: I(<>>),
lock: I(<>>),
network: I(<>>),
building: I(<>>),
briefcase: I(<>>),
check: I(<>>),
arrow: I(<>>),
mapPin: I(<>>),
trending: I(<>>),
zap: I(<>>),
handshake: I(<>>),
book: I(<>>),
cpu: I(<>>),
server: I(<>>),
scale: I(<>>),
compass: I(<>>),
flag: I(<>>),
heart: I(<>>),
bolt: I(<>>),
doc: I(<>>),
star: I(<>>)
};
const Icon = ({ name, size = 24, color = 'currentColor', sw = 1.75 }) => {
const C = ICN[name] || ICN.shield;
return ;
};
/* ── GEO WATERMARK ───────────────────────────────────────── */
const GeoWatermark = ({ color = V.purple, style }) =>
;
/* ── HERO WATERMARKS — one distinct, darker variant per page ─ */
const hexPts = (cx, cy, r) => [30, 90, 150, 210, 270, 330].map((a) => {
const rad = a * Math.PI / 180;
return `${(cx + r * Math.cos(rad)).toFixed(1)},${(cy + r * Math.sin(rad)).toFixed(1)}`;
}).join(' ');
const WM_VARIANTS = {
// Home — angular faceted grid + nested squares
arch: (c) => <>
{[0, 1, 2, 3, 4].map((i) => )}
{[0, 1, 2, 3].map((i) => )}
{[154, 110, 66].map((s, i) => )}
>,
// Services — concentric rings + radiating spokes
rings: (c) => <>
{[130, 96, 62, 30].map((r, i) => )}
{[0, 30, 60, 90, 120, 150, 180].map((a, i) => {const rad = a * Math.PI / 180;return ;})}
>,
// Toolkits — stacked layers + dot field
stack: (c) => <>
{[0, 1, 2, 3].map((i) => )}
{[0, 1, 2, 3, 4].map((r) => [0, 1, 2, 3, 4].map((cc) => ))}
>,
// Standards — hexagon tessellation
hex: (c) => <>
{[[305, 85], [365, 185], [305, 285], [200, 125], [200, 245], [140, 185]].map(([x, y], i) => )}
>,
// Partners — network graph
nodes: (c) => {
const pts = [[305, 75], [372, 155], [330, 255], [215, 95], [165, 205], [260, 180], [115, 120]];
const edges = [[5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [3, 0], [6, 3], [4, 2]];
return <>
{edges.map(([a, b], i) => )}
{pts.map(([x, y], i) => )}
{pts.map(([x, y], i) => )}
>;
},
// About — orbit rings + crosshair
orbit: (c) => <>
>,
// Contact — dot matrix + diagonal + square
grid: (c) => <>
{Array.from({ length: 6 }).map((_, r) => Array.from({ length: 8 }).map((_, cc) => ))}
>
};
const HeroWatermark = ({ variant = 'rings', color, opacity = 0.14, style }) => {
const c = color || V.purpleDark;
return (
);
};
/* ── HEADER ──────────────────────────────────────────────── */
const NAV = [
{ id: 'services', label: 'Services' },
{ id: 'training', label: 'Training' },
{ id: 'toolkits', label: 'Toolkits' },
{ id: 'standards', label: 'Standards' },
{ id: 'partners', label: 'Partners' },
{ id: 'resources', label: 'Resources' },
{ id: 'about', label: 'About Us' }];
const Header = ({ page, onNav }) => {
const active = (page || '').split(':')[0];
const [open, setOpen] = React.useState(false);
const go = (dest) => { setOpen(false); onNav(dest); };
return (
);
};
const hd = {
root: { position: 'sticky', top: 0, zIndex: 100, background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(12px)', borderBottom: `1px solid ${V.g200}`, height: 68 },
inner: { maxWidth: MAXW, margin: '0 auto', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 32px' },
logo: { display: 'flex', alignItems: 'center', cursor: 'pointer' },
nav: { display: 'flex', gap: 2 },
link: { fontSize: 14, fontWeight: 500, color: V.g600, padding: '8px 14px', borderRadius: 7, textDecoration: 'none', transition: 'all 150ms ease-out', fontFamily: FONT },
linkActive: { color: V.purple, fontWeight: 600, background: V.purpleSubtle },
actions: { display: 'flex', gap: 10, alignItems: 'center' },
btnGhost: { fontFamily: FONT, fontSize: 13, fontWeight: 600, padding: '9px 16px', borderRadius: 8, cursor: 'pointer', background: 'transparent', color: V.black, border: `1.5px solid ${V.g200}`, transition: 'all 150ms' },
btnPrimary: { fontFamily: FONT, fontSize: 13, fontWeight: 600, padding: '9px 18px', borderRadius: 8, cursor: 'pointer', background: V.purple, color: '#fff', border: 'none', transition: 'all 150ms' },
toggle: { display: 'none', alignItems: 'center', justifyContent: 'center', width: 40, height: 40, flexShrink: 0, background: 'transparent', color: V.black, border: `1.5px solid ${V.g200}`, borderRadius: 8, cursor: 'pointer', padding: 0 },
mobilePanel: { position: 'absolute', top: '100%', left: 0, right: 0, background: '#fff', borderBottom: `1px solid ${V.g200}`, boxShadow: '0 16px 32px rgba(0,0,0,0.10)', display: 'flex', flexDirection: 'column', padding: '8px 20px 24px', zIndex: 99, maxHeight: 'calc(100vh - 68px)', overflowY: 'auto' },
mobileLink: { display: 'block', padding: '13px 4px', fontSize: 15.5, fontWeight: 600, color: V.g700, textDecoration: 'none', borderBottom: `1px solid ${V.g100}`, fontFamily: FONT },
mobileLinkActive: { color: V.purple },
mobileActions: { display: 'flex', flexDirection: 'column', gap: 10, marginTop: 16 }
};
/* ── FOOTER ──────────────────────────────────────────────── */
const Footer = ({ onNav }) => {
const cols = [
['Services', [['Readiness Assessments', 'services'], ['Implementation Toolkits', 'toolkits'], ['Pre-Audit Preparation', 'services'], ['Formal Audits', 'services'], ['CCP Training', 'training']]],
['Standards', [['ISO 27001', 'standard:iso27001'], ['SOC 2', 'standard:soc2'], ['CMMC 2.0', 'standard:cmmc'], ['NIST', 'standard:nist']]],
['Resources', [['Webinars', 'resources'], ['Articles', 'resources'], ['Free Downloads', 'resources'], ['Knowledge Base', 'resources']]],
['Company', [['About Us', 'about'], ['Partners', 'partners'], ['Compliance by Design', 'about'], ['Start a Conversation', 'contact']]]];
return (
);
};
const ScrollTopBtn = () => {
const [vis, setVis] = React.useState(false);
React.useEffect(() => {
const h = () => setVis(window.pageYOffset > 500);
window.addEventListener('scroll', h);
return () => window.removeEventListener('scroll', h);
}, []);
if (!vis) return null;
return (
);
};
const ft = {
root: { background: V.charcoal, padding: '72px 0 0' },
inner: { maxWidth: MAXW, margin: '0 auto', padding: '0 32px' },
top: { display: 'grid', gridTemplateColumns: '360px 1fr', gap: 72, paddingBottom: 48, borderBottom: '1px solid rgba(255,255,255,0.08)' },
tagline: { fontSize: 14, color: 'rgba(255,255,255,0.5)', lineHeight: 1.65, margin: '0 0 22px', maxWidth: 320 },
markets: { display: 'flex', flexWrap: 'wrap', gap: 8 },
market: { fontSize: 11, fontWeight: 600, padding: '5px 11px', borderRadius: 9999, background: 'rgba(75,79,217,0.2)', color: '#9aa0f5', letterSpacing: '0.02em' },
cols: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 24 },
col: { display: 'flex', flexDirection: 'column', gap: 11 },
colHead: { fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)', marginBottom: 4 },
colLink: { fontSize: 13.5, color: 'rgba(255,255,255,0.62)', textDecoration: 'none', transition: 'color 150ms', fontFamily: FONT },
bottom: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '22px 0', gap: 24, flexWrap: 'wrap' },
copy: { fontSize: 12, color: 'rgba(255,255,255,0.3)' },
legal: { display: 'flex', gap: 8 },
cred: { fontSize: 10, fontWeight: 600, padding: '3px 8px', borderRadius: 4, background: 'rgba(255,255,255,0.06)', color: 'rgba(255,255,255,0.4)', letterSpacing: '0.04em', fontFamily: "'JetBrains Mono', monospace" }
};
/* ── UI PRIMITIVES ───────────────────────────────────────── */
const Overline = ({ children, color = V.orange, style }) =>
{children}
;
const SectionHeader = ({ overline, title, sub, align = 'left', maxSub = 560, dark = false }) =>
{overline &&
{overline}}
{title}
{sub &&
{sub}
}
;
const Btn = ({ children, kind = 'primary', onClick, style }) => {
const base = { fontFamily: FONT, fontSize: 15, fontWeight: 600, padding: '14px 28px', borderRadius: 9, cursor: 'pointer', border: 'none', transition: 'all 150ms ease-out', display: 'inline-flex', alignItems: 'center', gap: 8 };
const kinds = {
primary: { background: V.purple, color: '#fff' },
dark: { background: V.black, color: '#fff' },
accent: { background: V.orange, color: '#fff' },
ghost: { background: 'transparent', color: V.black, border: `1.5px solid ${V.g200}`, padding: '12.5px 26px' },
ghostLight: { background: 'transparent', color: '#fff', border: '1.5px solid rgba(255,255,255,0.25)', padding: '12.5px 26px' },
ghostBrand: { background: 'transparent', color: V.purple, border: `1.5px solid ${V.purple}`, padding: '12.5px 26px' }
};
return ;
};
const Tag = ({ children, color = V.g500, bg = V.g50, border = V.g200 }) =>
{children};
const Stat = ({ value, label, sub, color = V.black }) =>
{value}
{label}
{sub && {sub}}
;
// Full-bleed CTA band reused across pages — muted grey-purple, square corners
const CTABand = ({ overline = 'Ready to begin?', title, body, primary = 'Start a Conversation', onNav, dest = 'contact' }) =>
{overline}
{title}
{body}
onNav(dest)}>{primary}
;
// Page hero band (subtle grey, distinct darker watermark per page)
const PageHero = ({ overline, title, sub, children, variant = 'rings', wmColor, wmOpacity = 0.14 }) =>
{overline}
{title}
{sub &&
{sub}
}
{children}
;
const Section = ({ bg = '#fff', pad = '80px 0', children, style }) =>
;
/* ── LEAD CAPTURE MODAL — reused for gated PDFs + evaluation ─ */
const LeadCaptureModal = ({ icon = 'lock', eyebrow, heading, intro, cta = 'Continue', note, onClose, onSubmit }) => {
const [f, setF] = React.useState({ name: '', email: '', company: '', phone: '' });
const input = { fontFamily: FONT, fontSize: 14, color: V.black, background: '#fff', border: `1.5px solid ${V.g200}`, borderRadius: 8, padding: '11px 14px', outline: 'none', width: '100%' };
const label = { fontSize: 12.5, fontWeight: 600, color: V.black, display: 'block', marginBottom: 7 };
const field = (key, lbl, props = {}) =>
setF((v) => ({ ...v, [key]: e.target.value }))} {...props} />
;
return (
e.stopPropagation()} style={{ background: '#fff', borderRadius: 16, maxWidth: 480, width: '100%', boxShadow: '0 24px 64px rgba(0,0,0,0.3)', overflow: 'hidden', maxHeight: '92vh', overflowY: 'auto' }}>
{eyebrow &&
{eyebrow}
}
{heading}
);
};
Object.assign(window, {
V, MAXW, FONT, BrandMark, Icon, ICN, GeoWatermark,
Header, Footer, ScrollTopBtn, Overline, SectionHeader, Btn, Tag, Stat, CTABand, PageHero, Section,
HeroWatermark, LeadCaptureModal, submitInquiry
});