// 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 (
);
}
/* ------------------------------------------------------------------ */
/* 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) => (
))}
)}
);
}
/* ------------------------------------------------------------------ */
/* 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 */}
)}
{/* 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 (
{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,
});