/* Vooice — shared visual primitives. Style language: bone paper, ink line-art, signal-orange, sage-connected. Metaphor: the call as a physical object. Type as architecture. */ const VC_TOKENS = { paper: '#F4EFE6', paper2: '#ECE5D6', paper3: '#E2D9C5', ink: '#0E0E0F', ink2: '#2A2724', ink3: '#6A655C', line: '#1B1A18', lineSoft: 'rgba(14,14,15,0.10)', signal: '#F04E23', signalSoft: '#FCE3D8', sage: '#7A8A6E', sageSoft: '#DDE3D2', butter: '#E8C36A', }; // ── VLogo ─ logo mark with variants (controlled by Tweak: logoMark) ────────── function VLogo({ size = 22, color = '#0E0E0F', dot = '#F04E23', variant }) { const v = variant || (window.__vcLogoMark || 'halfdisc'); if (v === 'wordmark') return null; // wordmark-only mode: no glyph if (v === 'phone') { return ( ); } if (v === 'sound') { // concentric arcs — a sound emanating return ( ); } if (v === 'aperture') { // V-as-aperture: triangle nesting circle, dot bottom return ( ); } // default — halfdisc return ( ); } // ── Wordmark with mark ──────────────────────────────────────────────────────── function VWordmark({ size = 16, color = '#0E0E0F', dot = '#F04E23' }) { return ( vooice ); } // ── Mono label / system text ───────────────────────────────────────────────── function Mono({ children, color, size = 10, weight = 500, style = {}, upper = true, className }) { return ( {children} ); } // ── Tag / pill ─────────────────────────────────────────────────────────────── function Tag({ children, tone = 'ink', style = {}, className }) { const tones = { ink: { bg: 'transparent', fg: VC_TOKENS.ink, bd: VC_TOKENS.ink }, paper: { bg: VC_TOKENS.paper2, fg: VC_TOKENS.ink, bd: 'transparent' }, signal: { bg: VC_TOKENS.signal, fg: VC_TOKENS.paper, bd: 'transparent' }, signalSoft: { bg: VC_TOKENS.signalSoft, fg: VC_TOKENS.signal, bd: 'transparent' }, sage: { bg: VC_TOKENS.sage, fg: VC_TOKENS.paper, bd: 'transparent' }, sageSoft: { bg: VC_TOKENS.sageSoft, fg: '#3F4E33', bd: 'transparent' }, butter: { bg: VC_TOKENS.butter, fg: VC_TOKENS.ink, bd: 'transparent' }, ghost: { bg: 'transparent', fg: VC_TOKENS.ink3, bd: VC_TOKENS.lineSoft }, }; const t = tones[tone] || tones.ink; return ( {children} ); } // ── Live waveform — physical "voice as architecture" ───────────────────────── // renders a series of vertical bars whose heights breathe according to phase + a seeded amplitude. function Waveform({ bars = 64, height = 80, color = VC_TOKENS.ink, accent = VC_TOKENS.signal, active = true, speaker = 'vooice', // 'vooice' | 'callee' | 'silent' width = '100%', barWidth = 3, gap = 2, seed = 1, }) { const [t, setT] = React.useState(0); React.useEffect(() => { if (!active) return; let raf; const tick = () => { setT(p => p + 0.06); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [active]); const total = bars; const items = []; for (let i = 0; i < total; i++) { // pseudo-random per-bar amplitude + phase, modulated by speaker const r = Math.abs(Math.sin((i + 1) * 12.9898 * seed) * 43758.5453) % 1; const phase = i * 0.18 + r * 6.28; let amp = (Math.sin(t + phase) + 1) / 2; // 0..1 amp = amp * (0.5 + r * 0.6); if (speaker === 'silent') amp *= 0.05; if (speaker === 'callee') amp *= 0.55; const h = Math.max(2, amp * height); const isAccent = speaker === 'vooice' && (i % 7 === 0); items.push(
); } return (
{items}
); } // ── Concentric ring — "ringing" indicator ──────────────────────────────────── function RingingRing({ size = 24, color = '#F04E23' }) { return ( ); } // ── Stamp — letterpress-style accent box ───────────────────────────────────── function Stamp({ children, color = '#F04E23', style = {} }) { return ( {children} ); } // ── Hairline label row — "FIELD · value" key-value, used a lot ─────────────── function FieldRow({ label, children, align = 'space-between' }) { return (
{label} {children}
); } // ── Tape strip — used as data ribbons / call timeline ──────────────────────── function TapeStrip({ ticks = 40, color = VC_TOKENS.ink, height = 28, label }) { return (
{label && {label}}
{Array.from({length: ticks}).map((_,i) => (
))}
); } // ── Card surface — universal panel ─────────────────────────────────────────── function Surface({ children, padding = 24, bg = VC_TOKENS.paper, bordered = true, style = {} }) { return (
{children}
); } // ── Section header ─────────────────────────────────────────────────────────── function SectionHead({ eyebrow, title, action }) { return (
{eyebrow && {eyebrow}}
{title}
{action}
); } // ── Avatar — typographic, no images ────────────────────────────────────────── function Avatar({ name, size = 36, bg = VC_TOKENS.ink, fg = VC_TOKENS.paper }) { const initials = (name || '?').split(' ').slice(0,2).map(w => w[0]).join('').toUpperCase(); return ( {initials} ); } // ── Big number metric ──────────────────────────────────────────────────────── function Metric({ value, unit, label }) { return (
{value} {unit && {unit}}
{label}
); } // ── Phone glyph (thin line-art only — no emoji, no glowing icons) ──────────── function PhoneGlyph({ size = 14, color = 'currentColor', strokeWidth = 1.5 }) { return ( ); } function ArrowGlyph({ size = 14, dir = 'right', color = 'currentColor' }) { const rot = { right: 0, left: 180, up: -90, down: 90 }[dir] || 0; return ( ); } // ── Striped placeholder for imagery (we don't have any) ────────────────────── function StripedPlaceholder({ width = '100%', height = 200, label, color = VC_TOKENS.ink, bg = VC_TOKENS.paper2 }) { const stripe = `repeating-linear-gradient(135deg, ${bg} 0 12px, ${VC_TOKENS.paper3} 12px 13px)`; return (
{label}
); } // Expose to other Babel scripts // ── AppHeader ────────────────────────────────────────────────────────────── // Unified top nav for in-app screens. Use across Dashboard, OutboundCall, // CallDetail, ContactsMemory, Reminders, AuditLog, Pricing. // `current` highlights the active route key. `dark` flips palette for dark // canvases (Reminders). const APP_NAV = [ { l: 'Home', k: 'home' }, { l: 'Calls', k: 'outbound-call' }, { l: 'Reminders', k: 'reminders' }, { l: 'Memory', k: 'contacts-memory' }, { l: 'Audit', k: 'audit' }, ]; const APP_SUBNAV = [ { l: 'Pricing', k: 'pricing' }, { l: 'Landing', k: 'marketing-hero' }, ]; function AppHeader({ onNavigate, current, crumbs = [], dark = false, status }) { const fg = dark ? VC_TOKENS.paper : VC_TOKENS.ink; const fgMute = dark ? '#807a72' : VC_TOKENS.ink3; const bg = dark ? VC_TOKENS.ink : VC_TOKENS.paper; const lineCol = dark ? '#2a2724' : VC_TOKENS.line; const lineSoft = dark ? '#2a2724' : VC_TOKENS.lineSoft; // Hide the inline nav and collapse it under a hamburger on phone-sized // viewports. Using matchMedia keeps the header self-contained — no need to // thread vw through every screen. const [isMobile, setIsMobile] = React.useState( typeof window !== 'undefined' && window.matchMedia('(max-width: 720px)').matches ); const [open, setOpen] = React.useState(false); React.useEffect(() => { const mq = window.matchMedia('(max-width: 720px)'); const update = () => setIsMobile(mq.matches); if (mq.addEventListener) mq.addEventListener('change', update); else mq.addListener(update); return () => { if (mq.removeEventListener) mq.removeEventListener('change', update); else mq.removeListener(update); }; }, []); React.useEffect(() => { if (!isMobile) setOpen(false); }, [isMobile]); const go = (k) => { setOpen(false); onNavigate && onNavigate(k); }; return (
go('home')} style={{ cursor:'pointer' }}> {!isMobile && ( <> {crumbs.length > 0 && ( <>
{crumbs.map((c, i) => { const last = i === crumbs.length - 1; const item = typeof c === 'string' ? { label: c } : c; return ( item.k && go(item.k)} style={{ cursor: item.k ? 'pointer' : 'default' }} > {item.label} {!last && /} ); })}
)} )}
{!isMobile && ( <>
{APP_SUBNAV.map(it => ( go(it.k)} style={{ cursor:'pointer' }}> {it.l.toUpperCase()} ))}
{status && ( {status} )} +1 (415) 555‑0188 )} {isMobile && ( )}
{isMobile && open && (
{[...APP_NAV, ...APP_SUBNAV].map(it => { const active = it.k === current; return (
go(it.k)} role="menuitem" style={{ padding: '14px 0', borderBottom: `1px solid ${lineSoft}`, cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', }}> {it.l.toUpperCase()} {active && }
); })} {crumbs.length > 0 && (
{crumbs.map((c, i) => { const last = i === crumbs.length - 1; const item = typeof c === 'string' ? { label: c } : c; return ( item.k && go(item.k)} style={{ cursor: item.k ? 'pointer' : 'default' }}> {item.label} {!last && /} ); })}
)} {status &&
{status}
}
)}
); } // Expose to other Babel scripts Object.assign(window, { VC_TOKENS, VLogo, VWordmark, Mono, Tag, Waveform, RingingRing, Stamp, FieldRow, TapeStrip, Surface, SectionHead, Avatar, Metric, PhoneGlyph, ArrowGlyph, StripedPlaceholder, AppHeader, });