// mocks.jsx — Product UI mocks shared across all three concepts. // Renders the canonical TranscriptAgent primitives: claim cards, asset pulse rows, // theme heatmap, tracked-question notification, speaker spectrum, etc. const { useMemo } = React; /* ------------------------------------------------------------------ */ /* Tiny inline icons (stroke-only, ink color). Keep extremely simple. */ /* ------------------------------------------------------------------ */ function Ico({ name, size = 14, stroke = 'currentColor', strokeWidth = 1.6 }) { const s = { width: size, height: size, style: { flexShrink: 0, display: 'inline-block' } }; const p = { fill: 'none', stroke, strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round' }; switch (name) { case 'arrow-up': return (); case 'arrow-down': return (); case 'arrow-right': return (); case 'bolt': return (); case 'eye': return (); case 'check': return (); case 'cite': return (); case 'play': return (); case 'flask': return (); case 'bell': return (); case 'spark': return (); case 'compass': return (); case 'plus': return (); case 'search': return (); case 'logo': return ( ); default: return null; } } /* ------------------------------------------------------------------ */ /* Stance pill — bullish / bearish / neutral / mixed */ /* ------------------------------------------------------------------ */ function Stance({ value, conviction }) { const map = { bullish: { cls: 'chip--mint', label: 'Bullish', icon: 'arrow-up' }, bearish: { cls: 'chip--danger', label: 'Bearish', icon: 'arrow-down' }, neutral: { cls: 'chip--info', label: 'Neutral', icon: 'arrow-right' }, mixed: { cls: 'chip--warm', label: 'Mixed', icon: 'spark' }, }; const m = map[value] || map.neutral; return ( {m.label} {conviction != null && ( {[0,1,2].map(i => ( ))} )} ); } /* ------------------------------------------------------------------ */ /* Sparkline */ /* ------------------------------------------------------------------ */ function Sparkline({ points, width = 64, height = 22, color = 'var(--brand-deep)', fill = true }) { const min = Math.min(...points), max = Math.max(...points); const range = max - min || 1; const step = width / (points.length - 1); const d = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${(i*step).toFixed(2)},${(height - ((p - min) / range) * (height - 2) - 1).toFixed(2)}`).join(' '); const fillD = fill ? `${d} L${width},${height} L0,${height} Z` : null; return ( {fill && } ); } /* ------------------------------------------------------------------ */ /* Claim card — the atomic unit of the product */ /* ------------------------------------------------------------------ */ function ClaimCard({ claim, speaker, channel, stance = 'bullish', conviction = 2, asset, horizon = '3–6 mo', evidence = 4, timestamp = '14:32', variant = 'default', }) { const isWarm = variant === 'warm'; return (
{asset && {asset}} {horizon} {[0,1,2,3,4].map(i => ( ))}

“{claim}”

{speaker} · {channel} {timestamp}
); } /* ------------------------------------------------------------------ */ /* Asset Pulse row — asset / sparkline / momentum / mentions */ /* ------------------------------------------------------------------ */ function AssetPulseRow({ rank, ticker, name, mentions, speakers, momentum, sentiment, points, accent }) { const sign = momentum >= 0 ? '+' : ''; return (
{String(rank).padStart(2,'0')}
{ticker} {name}
{mentions} mentions · {speakers} speakers
= 0 ? 'chip--mint' : 'chip--danger'}`} style={{ fontSize: 11, justifyContent: 'center', fontWeight: 600 }}> = 0 ? 'arrow-up' : 'arrow-down'} size={10} strokeWidth={2.2} /> {sign}{momentum}% {sentiment}
); } /* ------------------------------------------------------------------ */ /* Theme heatmap */ /* ------------------------------------------------------------------ */ function ThemeHeatmap({ themes, weeks = 8, compact = false }) { return (
{themes.map((row, i) => (
{row.label}
{row.heat.slice(0, weeks).map((h, j) => { const a = Math.max(0.06, Math.min(1, h)); const bg = row.tone === 'cool' ? `rgba(35,80,102, ${a})` : row.tone === 'mint' ? `rgba(23,56,45, ${a})` : `rgba(122,77,10, ${a})`; return (
); })}
= 0 ? 'chip--mint' : 'chip--cool'}`} style={{ fontSize: 10.5, justifyContent: 'center', fontWeight: 600, padding: '3px 8px' }}> {row.delta >= 0 ? '+' : ''}{row.delta}
))}
); } /* ------------------------------------------------------------------ */ /* Tracked Question card */ /* ------------------------------------------------------------------ */ function TrackedQuestion({ q, newCount = 2, since = 'this week', preview }) { return (
Tracked question · {newCount} new answers {since}

“{q}”

{preview && (
{preview.map((p, i) => (
{p.speaker} — {p.line}
))}
)}
); } /* ------------------------------------------------------------------ */ /* Speaker Spectrum — bear→bull axis with speaker dots */ /* ------------------------------------------------------------------ */ function SpeakerSpectrum({ asset, speakers }) { // speakers: [{name, x: 0..1, conviction, prevX?: 0..1}] return (
Speaker spectrum · {asset}
{/* axis */}
{speakers.map((s, i) => { const top = i % 2 === 0 ? 8 : 92; const hasPrev = typeof s.prevX === 'number' && Math.abs(s.prevX - s.x) > 0.02; return ( {/* ghost (prev position) + arrow */} {hasPrev && (
{/* ghost dot at prevX */} {/* connector + arrow */} {s.prevX < s.x ? ( <> ) : ( <> )}
)} {/* speaker label + dot */}
{i % 2 === 0 && ( <>
{s.name} {hasPrev && }
)}
0.6 ? 'var(--brand-deep)' : 'var(--gold)', border: '2px solid #fff', boxShadow: '0 0 0 1px rgba(28,51,67,0.15)', position: i % 2 === 0 ? 'relative' : 'absolute', bottom: i % 2 === 0 ? 'auto' : 28, }} /> {i % 2 === 1 && ( <>
{s.name} {hasPrev && }
)}
); })}
BearishNeutralBullish
); } /* ------------------------------------------------------------------ */ /* Channel row — a “followed channel” entry */ /* ------------------------------------------------------------------ */ function ChannelRow({ name, kind, freq, active = true, accent = 'gold' }) { const colors = { gold: 'linear-gradient(135deg, #e7c46c, #7a4d0a)', mint: 'linear-gradient(135deg, #c8e1d2, #17382d)', bronze: 'linear-gradient(135deg, #d8b07a, #5e3c10)', cool: 'linear-gradient(135deg, #c5dae9, #235066)', slate: 'linear-gradient(135deg, #b6c1cc, #3a4a55)', }; return (
{name}
{kind} · {freq}
{active ? <> Live : 'Paused'}
); } /* ------------------------------------------------------------------ */ /* Citation block */ /* ------------------------------------------------------------------ */ function Citation({ video, speaker, timestamp = '14:32', evidence = 4 }) { return (
{video} · {speaker} {timestamp}
); } /* Expose to other Babel scripts */ Object.assign(window, { Ico, Stance, Sparkline, ClaimCard, AssetPulseRow, ThemeHeatmap, TrackedQuestion, SpeakerSpectrum, ChannelRow, Citation, });