// ════════════════════════════════════════════════════════════════
// StartupCard — детальная карточка компании из реестра
// Открывается при клике на строку CorpusPage
// ════════════════════════════════════════════════════════════════
const { useMemo: useMemoStartup } = React;
function StartupCard({ startup, onClose, onOpenSynthesis }) {
const D = window.SDH_DATA;
const s = startup;
// generate deterministic mock detail data from id
const seed = s.id;
function pr(n) { return ((seed * 9301 + n * 49297) % 233280) / 233280; }
const fundingRound = s.funding_usd ? (pr(1) < 0.4 ? 'Seed' : pr(1) < 0.7 ? 'Series A' : 'Series B') : 'pre-seed';
const teamSize = 3 + Math.floor(pr(2) * 28);
const founded = 2018 + Math.floor(pr(3) * 7);
const website = `${s.name.toLowerCase().replace(/\s+/g, '')}.com`;
// related ideas (5 from corpus seed)
const relatedIdeas = useMemoStartup(() => {
return Array.from({ length: 3 }).map((_, k) => {
const idx = Math.floor(pr(10 + k) * D.ideas.length);
return D.ideas[idx];
});
}, [seed]);
// mention timeseries (28 days)
const timeseries = useMemoStartup(() => {
return Array.from({ length: 28 }).map((_, t) => ({
value: Math.max(0, Math.round((pr(t + 50) * 8) + (t === 27 ? 6 : 0))),
}));
}, [seed]);
const totalMentions = timeseries.reduce((sum, p) => sum + p.value, 0);
// signal tags
const tags = useMemoStartup(() => {
const all = ['B2B', 'B2C', 'enterprise', 'SaaS', 'open-source', 'API-first', 'on-prem', 'compliance', 'мобильное', 'edge'];
return all.filter((_, i) => pr(100 + i) > 0.65).slice(0, 4);
}, [seed]);
const similarityZone =
s.similarity < 0.5 ? { hue: 'rose', label: 'низкая' } :
s.similarity < 0.75 ? { hue: 'amber', label: 'средняя' } :
{ hue: 'emerald', label: 'высокая' };
return (
e.stopPropagation()} className="sdh-fade" style={{
background: 'var(--surface-elevated)',
borderRadius: 'var(--radius-2xl)',
border: '1px solid var(--border-subtle)',
maxWidth: 980, width: '100%',
boxShadow: 'var(--shadow-xl)',
position: 'relative',
overflow: 'hidden',
}}>
{/* close */}
{/* HERO — tinted by niche hue */}
стартап № {s.id}
·
{s.first_seen_at}
{s.name}
{s.desc}
{s.nicheLabel}
{s.sourceLabel}
{s.geo}
{s.funding_usd && (
${(s.funding_usd / 1000000).toFixed(1)}M
{fundingRound}
)}
{/* BODY — 2-column grid */}
{/* LEFT */}
{/* Quick facts */}
{/* Mentions timeline */}
упоминания за 28 дней
{totalMentions}
всего упоминаний
последнее: {s.daysAgo === 0 ? 'сегодня' : `${s.daysAgo}д назад`}
{/* Signals / tags */}
сигнальные теги
{tags.length === 0 && нет распознанных тегов }
{tags.map(t => {t} )}
{/* RIGHT */}
{/* Similarity */}
релевантность исследованию
{s.similarity.toFixed(2)}
{similarityZone.label}
пороги: 0.50 · 0.75
{/* Related ideas */}
связанные идеи стартапзавода
{relatedIdeas.map((idea, k) => (
e.currentTarget.style.borderColor = 'var(--brand-bronze)'}
onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border-subtle)'}>
№{String(idea.number).padStart(2, '0')}
{idea.title.length > 50 ? idea.title.slice(0, 48) + '…' : idea.title}
{idea.familyLabel}
))}
{/* Status / next action */}
статус наблюдения
{s.similarity >= 0.75
? 'Высокая близость к фокусу исследования. Рекомендуем поставить на еженедельный мониторинг.'
: s.similarity >= 0.5
? 'Средняя релевантность. Дополнительный сигнал нужен для принятия решения.'
: 'Низкая близость. Включён в корпус как контекстная единица.'}
{/* FOOTER actions */}
esc закрыть · j /k между карточками
Закрыть
{
const csv = [
['id', 'name', 'niche', 'source', 'website', 'team_size', 'first_seen'],
[startup.id, startup.name, startup.niche || '', startup.source || '', startup.website || '', startup.team_size ?? '', startup.first_seen_at || ''],
].map(r => r.map(v => `"${String(v).replace(/"/g, '""')}"`).join(',')).join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `startup-${startup.id}-${(startup.name || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 40)}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}}>
Экспорт CSV
0
? `Открыть синтез-сессию по идее №${relatedIdeas[0].number}`
: 'Синтез-сессия привязана к идее. У этого стартапа пока нет связанной идеи.'}
disabled={!relatedIdeas || relatedIdeas.length === 0 || !onOpenSynthesis}
onClick={() => {
if (relatedIdeas && relatedIdeas.length > 0 && onOpenSynthesis) {
onClose && onClose();
onOpenSynthesis(relatedIdeas[0]);
}
}}>
Открыть в синтез-сессии
);
}
function Fact({ label, value, link }) {
return (
);
}
window.StartupCard = StartupCard;