// Components.jsx — LexiPanel for Defense (PRD-2026-05-21-MILTECH-DSS) // Single-file React + Babel-standalone pattern (no build step). // Day 4: DemoRiskScore state machine + INPUT_FORM + SCORE_DISPLAY + FALLBACK_STATIC. // Style: DLI engineering register — no emoji, no exclamation, numbers preferred. // ============================================================================ // 1) DLI TOKENS (vendored from DLI-NEWSLETTER/DLITemplates.jsx — DO NOT EDIT here) // ============================================================================ const DLI_PALETTE = { bg: '#0a1024', bgDeep: '#070b1a', surface: '#0f172a', surfaceCard: '#111a36', border: 'rgba(148,163,184,0.12)', borderStrong: 'rgba(148,163,184,0.22)', fg: '#f8fafc', fgMuted: '#94a3b8', fgSubtle: '#64748b', lime: '#84cc16', limeBright: '#a3e635', amber: '#e0a82e', amberBright: '#f59e0b', indigo: '#6366f1', indigoBright: '#818cf8', red: '#ef4444', }; const API_ENDPOINT = '/api/v1/score-risk-miltech'; const SUBMIT_TIMEOUT_MS = 3000; // ============================================================================ // 2) BILINGUAL TRANSLATION MAP // ============================================================================ const T = { brand_name: { en: 'LexiPanel', ua: 'LexiPanel' }, brand_vertical: { en: 'for Defense', ua: 'для Оборони' }, hero_eyebrow: { en: 'UA Online Miltech Conference 2026 · ShowCases · 30 May', ua: 'UA Online Miltech Conference 2026 · ShowCases · 30 травня' }, hero_h1_a: { en: 'Score any miltech decision.', ua: 'Оціни будь-яке мілтех-рішення.' }, hero_h1_b: { en: 'See which factors moved the needle.', ua: 'Побач, які фактори зрушили стрілку.' }, hero_lede: { en: 'A KAN-powered Universal Risk Score with the symbolic formula your auditor can read — not a black-box opinion.', ua: 'KAN-оцінка універсального ризику з символьною формулою, яку прочитає твій аудитор — а не чорний ящик.' }, cta_primary: { en: 'Get your indicative score', ua: 'Отримати індикативну оцінку' }, cta_secondary: { en: '15-min feedback chat', ua: '15-хв розмова про відгук' }, section_router_label: { en: 'WHO ARE YOU SCORING FOR?', ua: 'ХТО ОЦІНЮЄ?' }, section_router_title: { en: 'Three roles. One model. Formulas you can audit.', ua: 'Три ролі. Одна модель. Формули, які витримують аудит.' }, role_defense_key: { en: '01 · DEFENSE', ua: '01 · ОБОРОНА' }, role_defense_title: { en: 'Defense company', ua: 'Оборонна компанія' }, role_defense_body: { en: 'You build for the front. Score your next contract for legal, dual-use, and sanctions risk before you bid.', ua: 'Ти будуєш для фронту. Оціни свій наступний контракт на юридичний, dual-use та санкційний ризик до подачі заявки.' }, role_dualuse_key: { en: '02 · DUAL-USE', ua: '02 · DUAL-USE' }, role_dualuse_title: { en: 'Dual-use business', ua: 'Dual-use бізнес' }, role_dualuse_body: { en: 'Civilian core, defense edge. Evaluate the regulatory and export-control gap before you cross it.', ua: 'Цивільне ядро, оборонний край. Оціни регуляторний та експортно-контрольний розрив до того, як перетнеш його.' }, role_investor_key: { en: '03 · INVESTOR', ua: '03 · ІНВЕСТОР' }, role_investor_title: { en: 'Miltech investor', ua: 'Мілтех-інвестор' }, role_investor_body: { en: 'You back miltech. Stress-test a portfolio company risk profile in 90 seconds.', ua: 'Ти інвестуєш у мілтех. Стрес-тест ризик-профілю портфельної компанії за 90 секунд.' }, role_cta: { en: 'Score as this role →', ua: 'Оцінити в цій ролі →' }, // Demo state machine — INPUT_FORM form_title_defense: { en: 'Score this contract', ua: 'Оцінити цей контракт' }, form_title_dualuse: { en: 'Score this deal', ua: 'Оцінити цю угоду' }, form_title_investor: { en: 'Score this portfolio company', ua: 'Оцінити цю портфельну компанію' }, form_sub: { en: 'Seven factors. KAN inference + symbolic formula in under a second.', ua: 'Сім факторів. KAN-інференс + символьна формула менш ніж за секунду.' }, form_back: { en: '← Back to roles', ua: '← Назад до ролей' }, form_submit: { en: 'Calculate risk score', ua: 'Розрахувати оцінку ризику' }, field_value_label_defense: { en: 'Contract value (EUR)', ua: 'Вартість контракту (EUR)' }, field_value_label_dualuse: { en: 'Deal value (EUR)', ua: 'Вартість угоди (EUR)' }, field_value_label_investor: { en: 'Round size (EUR)', ua: 'Розмір раунду (EUR)' }, field_value_help: { en: 'Stored as log10(value). 1k → 3, 1M → 6, 100M → 8.', ua: 'Зберігається як log10(value). 1k → 3, 1М → 6, 100М → 8.' }, field_country_label: { en: 'Counterparty jurisdiction', ua: 'Юрисдикція контрагента' }, field_country_opt_1: { en: '1 — EU/NATO core (DE, FR, UK, US)', ua: '1 — Ядро EU/NATO (DE, FR, UK, US)' }, field_country_opt_2: { en: '2 — EU/NATO periphery (PL, IT, ES, TR)', ua: '2 — Периферія EU/NATO (PL, IT, ES, TR)' }, field_country_opt_3: { en: '3 — Allied non-NATO (UA, IL, AU, KR)', ua: '3 — Союзники non-NATO (UA, IL, AU, KR)' }, field_country_opt_4: { en: '4 — Neutral (CH, IN, AR, BR)', ua: '4 — Нейтральні (CH, IN, AR, BR)' }, field_country_opt_5: { en: '5 — High-risk (RU, IR, NK, CN)', ua: '5 — Високого ризику (RU, IR, NK, CN)' }, field_dualuse_label: { en: 'Dual-use class (EU Reg 2021/821)', ua: 'Dual-use клас (EU Reg 2021/821)' }, field_dualuse_opt_0: { en: '0 — Pure civilian (no dual-use)', ua: '0 — Суто цивільне (без dual-use)' }, field_dualuse_opt_1: { en: '1 — Annex I controlled (license required)', ua: '1 — Annex I контрольоване (потрібна ліцензія)' }, field_dualuse_opt_2: { en: '2 — Wassenaar Cat 2-9 (specific authorization)', ua: '2 — Wassenaar Cat 2-9 (спецдозвіл)' }, field_dualuse_opt_3: { en: '3 — Wassenaar Cat 1 (military)', ua: '3 — Wassenaar Cat 1 (військове)' }, field_experience_label: { en: 'Team defense experience (years, sum across team)', ua: 'Досвід команди в обороні (років, сума по команді)' }, field_overlap_label: { en: 'Overlapping regulatory regimes', ua: 'Регуляторні регіми (накладання)' }, field_overlap_help: { en: 'e.g. EU + UA + NATO + ITAR = 4', ua: 'напр. EU + UA + NATO + ITAR = 4' }, field_sanctions_label: { en: 'Counterparty distance from sanctions lists', ua: 'Відстань контрагента від санкційних списків' }, field_sanctions_opt_0: { en: '0 — Direct hit (sanctioned)', ua: '0 — Пряме потрапляння (під санкціями)' }, field_sanctions_opt_1: { en: '1 — 1 hop (board member sanctioned)', ua: '1 — 1 hop (член ради під санкціями)' }, field_sanctions_opt_2: { en: '2 — 2 hops (UBO sanctioned)', ua: '2 — 2 hops (UBO під санкціями)' }, field_sanctions_opt_3: { en: '3 — Clean (no nexus found)', ua: '3 — Чисто (звʼязків не знайдено)' }, field_trl_label: { en: 'Technology Readiness Level (NATO TRL)', ua: 'Рівень готовності технології (NATO TRL)' }, field_trl_opt_1: { en: '1 — Basic principles observed', ua: '1 — Базові принципи спостерігаються' }, field_trl_opt_2: { en: '2 — Technology concept formulated', ua: '2 — Концепт сформульовано' }, field_trl_opt_3: { en: '3 — Experimental proof of concept', ua: '3 — Експериментальний PoC' }, field_trl_opt_4: { en: '4 — Lab validation', ua: '4 — Валідація в лабораторії' }, field_trl_opt_5: { en: '5 — Validation in relevant environment', ua: '5 — Валідація у релевантному середовищі' }, field_trl_opt_6: { en: '6 — Demonstration in relevant environment', ua: '6 — Демонстрація у релевантному середовищі' }, field_trl_opt_7: { en: '7 — Operational environment prototype', ua: '7 — Прототип в операційному середовищі' }, field_trl_opt_8: { en: '8 — System complete and qualified', ua: '8 — Система завершена і кваліфікована' }, field_trl_opt_9: { en: '9 — Proven in operational use', ua: '9 — Підтверджена в операційному використанні' }, // Demo state machine — SUBMITTING submitting_text: { en: 'Calling KAN inference', ua: 'Виклик KAN-інференсу' }, // Demo state machine — SCORE_DISPLAY score_label: { en: 'RISK SCORE', ua: 'ОЦІНКА РИЗИКУ' }, score_factors_label: { en: 'TOP 3 RISK FACTORS', ua: '3 ТОП-ФАКТОРИ РИЗИКУ' }, score_formula_label: { en: 'SYMBOLIC FORMULA (KAN-LEARNED)', ua: 'СИМВОЛЬНА ФОРМУЛА (ВИВЧЕНА KAN)' }, score_meta_label: { en: 'INFERENCE META', ua: 'МЕТА ІНФЕРЕНСУ' }, score_cta_apply: { en: 'Apply this to my real case →', ua: 'Застосувати до мого реального кейсу →' }, score_cta_feedback: { en: '15-min feedback chat', ua: '15-хв розмова про відгук' }, score_back: { en: '← Try another role', ua: '← Інша роль' }, score_label_low: { en: 'LOW', ua: 'НИЗЬКА' }, score_label_moderate: { en: 'MODERATE', ua: 'ПОМІРНА' }, score_label_high: { en: 'ELEVATED', ua: 'ПІДВИЩЕНА' }, score_factor_increases: { en: 'increases risk', ua: 'підвищує ризик' }, score_factor_decreases: { en: 'decreases risk', ua: 'знижує ризик' }, // Fallback state fallback_banner: { en: 'KAN inference offline — score computed client-side from the disclosed formula.', ua: 'KAN-інференс офлайн — оцінка обчислена клієнтом за оприлюдненою формулою.' }, indicative: { en: 'Indicative — synthetic-calibrated v0 (Art.21 KE2)', ua: 'Індикативно — синтетична калібровка v0 (Art.21 KE2)' }, // Demo intro (when no role selected yet) demo_stub_pre: { en: 'Pick a role above to score a deal. Backend live at', ua: 'Обери роль вище, щоб оцінити угоду. Бекенд працює на' }, demo_stub_post: { en: '— returns score + symbolic formula + top-3 factors.', ua: '— повертає score + символьну формулу + 3 топ-фактори.' }, // Feature name labels (used in top_factors display) feature_contract_value_eur_log: { en: 'Contract value', ua: 'Вартість контракту' }, feature_country_risk_class: { en: 'Counterparty jurisdiction', ua: 'Юрисдикція контрагента' }, feature_dual_use_export_class: { en: 'Dual-use classification', ua: 'Dual-use класифікація' }, feature_team_defense_experience_years: { en: 'Team defense experience', ua: 'Досвід команди в обороні' }, feature_regulatory_overlap_count: { en: 'Regulatory overlap', ua: 'Регуляторне накладання' }, feature_counterparty_sanctions_distance: { en: 'Sanctions distance', ua: 'Відстань санкцій' }, feature_trl_level: { en: 'TRL level', ua: 'Рівень TRL' }, cred_label_engine: { en: 'INFERENCE ENGINE', ua: 'INFERENCE ENGINE' }, cred_val_engine: { en: 'KAN Phase 8.1', ua: 'KAN Фаза 8.1' }, cred_label_arch: { en: 'ARCHITECTURE', ua: 'АРХІТЕКТУРА' }, cred_val_arch: { en: 'FastKAN [7,4,1]', ua: 'FastKAN [7,4,1]' }, cred_label_speaker: { en: 'SPEAKER', ua: 'СПІКЕР' }, cred_val_speaker: { en: 'Yaroslav Baienko', ua: 'Ярослав Баєнко' }, cred_val_speaker_role:{ en: 'GOLAW · Dual MSc Law + CS', ua: 'GOLAW · Подвійний магістр Право + ІТ' }, cred_label_event: { en: 'EVENT', ua: 'ПОДІЯ' }, cred_val_event: { en: '30 May 2026', ua: '30 травня 2026' }, footer_brand: { en: 'LexiPanel for Defense', ua: 'LexiPanel для Оборони' }, footer_byline: { en: 'KAN-powered strategic decision support for defense, dual-use, investor decisions.', ua: 'KAN-підтримка стратегічних рішень для оборони, dual-use, інвесторів.' }, footer_meta: { en: 'PRD-2026-05-21-MILTECH-DSS · LexiPanel vertical · Hetzner + Caddy + KAN', ua: 'PRD-2026-05-21-MILTECH-DSS · LexiPanel вертикаль · Hetzner + Caddy + KAN' }, }; const t = (key, lang) => (T[key] && T[key][lang]) || (T[key] && T[key].en) || key; // ============================================================================ // 3) FORM DEFAULTS + CLIENT-SIDE FORMULA (matches kan-engine/main.py disclosed formula) // ============================================================================ const DEFAULT_FORM = { contract_value_eur_log: 5.0, // EUR 100k country_risk_class: 3, dual_use_export_class: 1, team_defense_experience_years: 5, regulatory_overlap_count: 2, counterparty_sanctions_distance: 3, trl_level: 6, }; // In-form display: contract value as EUR (convert log10 ↔ EUR via 10^x) const logToEur = (log) => Math.round(Math.pow(10, log)); const eurToLog = (eur) => { if (!eur || eur <= 0) return 3; return Math.max(3, Math.min(18, Math.log10(eur))); }; function sigmoid(x) { if (x >= 0) return 1 / (1 + Math.exp(-x)); const e = Math.exp(x); return e / (1 + e); } // Client-side replica of /v1/score-risk-miltech for FALLBACK_STATIC // MUST match kan-engine/app/main.py _MILTECH_FORMULA_TEXT byte-for-byte. function clientSideFormula(v) { const raw = 0.31 * Math.tanh(v.contract_value_eur_log - 12) + 0.18 * (v.country_risk_class - 3) + 0.21 * v.dual_use_export_class - 0.12 * Math.tanh(v.team_defense_experience_years - 5) + 0.09 * (v.regulatory_overlap_count - 1) * (3 - v.counterparty_sanctions_distance) - 0.15 * (v.trl_level - 5) / 4 + 0.1; return Math.max(0, Math.min(1, sigmoid(raw))); } function clientSideTopFactors(v) { const coupling = 0.09 * (v.regulatory_overlap_count - 1) * (3 - v.counterparty_sanctions_distance); const contribs = { contract_value_eur_log: 0.31 * Math.tanh(v.contract_value_eur_log - 12), country_risk_class: 0.18 * (v.country_risk_class - 3), dual_use_export_class: 0.21 * v.dual_use_export_class, team_defense_experience_years: -0.12 * Math.tanh(v.team_defense_experience_years - 5), regulatory_overlap_count: coupling * 0.5, counterparty_sanctions_distance: coupling * 0.5, trl_level: -0.15 * (v.trl_level - 5) / 4, }; return Object.entries(contribs) .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1])) .slice(0, 3) .map(([feature, c]) => ({ feature, contribution: Math.round(c * 10000) / 10000, direction: c > 0 ? 'increases' : 'decreases', })); } const FORMULA_TEXT = 'sigmoid(0.31*tanh(contract_value_eur_log - 12) + 0.18*(country_risk_class - 3) + ' + '0.21*dual_use_export_class - 0.12*tanh(team_defense_experience_years - 5) + ' + '0.09*(regulatory_overlap_count - 1)*(3 - counterparty_sanctions_distance) - ' + '0.15*(trl_level - 5)/4 + 0.1)'; // ============================================================================ // 4) UI ATOMS // ============================================================================ function BrandMark() { return ( ); } function LangToggle({ lang, setLang }) { return (
); } // ============================================================================ // 5) HEADER / HERO / ROLE ROUTER // ============================================================================ function Header({ lang, setLang }) { return (
{t('brand_name', lang)} {t('brand_vertical', lang)}
); } function Hero({ lang, onPrimaryCTA }) { return (
{t('hero_eyebrow', lang)}

{t('hero_h1_a', lang)}
{t('hero_h1_b', lang)}

{t('hero_lede', lang)}

{t('indicative', lang)}
); } function RoleCard({ accent, keyText, title, body, ctaText, onClick, isActive }) { return ( ); } function RoleRouter({ lang, currentRole, onRoleSelect }) { return (
{t('section_router_label', lang)}

{t('section_router_title', lang)}

onRoleSelect('defense')} /> onRoleSelect('dualuse')} /> onRoleSelect('investor')} />
); } // ============================================================================ // 6) DEMO STATE MACHINE: ROLE_SELECT → INPUT_FORM → SUBMITTING → SCORE_DISPLAY // ↓ (5xx / timeout) // FALLBACK_STATIC // ============================================================================ function DemoIntro({ lang }) { return (
{t('demo_stub_pre', lang)} POST /api/v1/score-risk-miltech {t('demo_stub_post', lang)}
); } function NumberField({ id, value, onChange, min, max, step, help }) { return (
onChange(e.target.value === '' ? '' : Number(e.target.value))} /> {help &&
{help}
}
); } function SelectField({ id, value, onChange, options }) { return ( ); } function InputForm({ lang, role, formData, setFormData, onSubmit, onBack }) { // contract value is shown to user as EUR but stored as log10 const eurValue = logToEur(formData.contract_value_eur_log); const titleKey = role === 'dualuse' ? 'form_title_dualuse' : role === 'investor' ? 'form_title_investor' : 'form_title_defense'; const valueLabelKey = role === 'dualuse' ? 'field_value_label_dualuse' : role === 'investor' ? 'field_value_label_investor' : 'field_value_label_defense'; const setField = (k, v) => setFormData({ ...formData, [k]: v }); return (

{t(titleKey, lang)}

{t('form_sub', lang)}

setField('contract_value_eur_log', eur === '' ? 3 : eurToLog(eur))} help={t('field_value_help', lang)} />
setField('country_risk_class', v)} options={[ [1, t('field_country_opt_1', lang)], [2, t('field_country_opt_2', lang)], [3, t('field_country_opt_3', lang)], [4, t('field_country_opt_4', lang)], [5, t('field_country_opt_5', lang)], ]} />
setField('dual_use_export_class', v)} options={[ [0, t('field_dualuse_opt_0', lang)], [1, t('field_dualuse_opt_1', lang)], [2, t('field_dualuse_opt_2', lang)], [3, t('field_dualuse_opt_3', lang)], ]} />
setField('team_defense_experience_years', Math.max(0, Math.min(15, Number(v) || 0)))} />
setField('regulatory_overlap_count', Math.max(0, Math.min(5, Number(v) || 0)))} help={t('field_overlap_help', lang)} />
setField('counterparty_sanctions_distance', v)} options={[ [0, t('field_sanctions_opt_0', lang)], [1, t('field_sanctions_opt_1', lang)], [2, t('field_sanctions_opt_2', lang)], [3, t('field_sanctions_opt_3', lang)], ]} />
setField('trl_level', v)} options={[1,2,3,4,5,6,7,8,9].map(n => [n, t('field_trl_opt_' + n, lang)])} />
); } function Submitting({ lang }) { return (
{t('submitting_text', lang)} POST {API_ENDPOINT}
); } function ScoreDisplay({ lang, response, isFallback, onReset, onLead }) { const score = response.score_0_100; const tier = response.risk_label; const tierClass = tier === 'high' ? 'risk-elevated' : tier === 'moderate' ? 'risk-moderate' : 'risk-low'; const tierLabelKey = tier === 'high' ? 'score_label_high' : tier === 'moderate' ? 'score_label_moderate' : 'score_label_low'; return (
{isFallback &&
{t('fallback_banner', lang)}
}
{score}
/ 100
{t(tierLabelKey, lang)}
{t('indicative', lang)}
{t('score_factors_label', lang)}
    {response.top_factors.map((f, i) => (
  • {t('feature_' + f.feature, lang)}
    {t(f.direction === 'increases' ? 'score_factor_increases' : 'score_factor_decreases', lang)} {f.contribution >= 0 ? '+' : ''}{f.contribution.toFixed(3)}
  • ))}
{t('score_formula_label', lang)}
{response.formula_text || FORMULA_TEXT}
{t('score_meta_label', lang)}
model: {response.model_id} latency: {response.latency_ms}ms {isFallback && mode: fallback-static}
); } function DemoRiskScore({ lang, currentRole, onRoleReset, onLeadCapture }) { const [state, setState] = React.useState('IDLE'); // IDLE | INPUT_FORM | SUBMITTING | SCORE_DISPLAY | FALLBACK_STATIC const [formData, setFormData] = React.useState(DEFAULT_FORM); const [response, setResponse] = React.useState(null); const [isFallback, setIsFallback] = React.useState(false); // When user picks a role above, transition to INPUT_FORM React.useEffect(() => { if (currentRole && state === 'IDLE') setState('INPUT_FORM'); }, [currentRole, state]); const handleSubmit = React.useCallback(async () => { setState('SUBMITTING'); setIsFallback(false); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), SUBMIT_TIMEOUT_MS); try { const r = await fetch(API_ENDPOINT, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ entity: formData, metadata: { caller: 'miltech-landing', role: currentRole } }), signal: controller.signal, }); clearTimeout(timeoutId); if (!r.ok) throw new Error('http_' + r.status); const data = await r.json(); setResponse(data); setState('SCORE_DISPLAY'); } catch (_err) { // FALLBACK_STATIC — client-side formula eval clearTimeout(timeoutId); const risk = clientSideFormula(formData); const score = Math.round(risk * 100); const label = risk >= 0.66 ? 'high' : (risk >= 0.33 ? 'moderate' : 'low'); setResponse({ risk_0_1: risk, risk_label: label, score_0_100: score, top_factors: clientSideTopFactors(formData), formula_text: FORMULA_TEXT, model_id: 'client-side-fallback', latency_ms: 1, }); setIsFallback(true); setState('FALLBACK_STATIC'); } }, [formData, currentRole]); const handleReset = React.useCallback(() => { setState('IDLE'); setResponse(null); setIsFallback(false); onRoleReset(); }, [onRoleReset]); const handleBack = React.useCallback(() => { setState('IDLE'); onRoleReset(); }, [onRoleReset]); return (
{state === 'IDLE' && } {state === 'INPUT_FORM' && ( )} {state === 'SUBMITTING' && } {(state === 'SCORE_DISPLAY' || state === 'FALLBACK_STATIC') && response && ( )}
); } // ============================================================================ // 7) CREDIBILITY + FOOTER // ============================================================================ function CredibilityStrip({ lang }) { return (
{t('cred_val_engine', lang)}
{t('cred_label_engine', lang)}
{t('cred_val_arch', lang)}
{t('cred_label_arch', lang)}
{t('cred_val_speaker', lang)}
{t('cred_val_speaker_role', lang)}
{t('cred_val_event', lang)}
{t('cred_label_event', lang)}
); } function Footer({ lang, version }) { return ( ); } // ============================================================================ // 8) ROOT // ============================================================================ function detectLang() { if (typeof navigator === 'undefined' || !navigator.language) return 'en'; return navigator.language.toLowerCase().startsWith('uk') ? 'ua' : 'en'; } function Page() { const [lang, setLangRaw] = React.useState(detectLang()); const [currentRole, setCurrentRole] = React.useState(null); const setLang = React.useCallback((l) => { setLangRaw(l); document.documentElement.lang = l === 'ua' ? 'uk' : 'en'; }, []); React.useEffect(() => { document.documentElement.lang = lang === 'ua' ? 'uk' : 'en'; }, [lang]); const scrollToRouter = React.useCallback(() => { const el = document.getElementById('role-router'); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, []); const handleRoleSelect = React.useCallback((role) => { setCurrentRole(role); // give React a tick to mount the form, then scroll setTimeout(() => { const el = document.getElementById('demo'); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 50); }, []); const handleRoleReset = React.useCallback(() => { setCurrentRole(null); setTimeout(() => { const el = document.getElementById('role-router'); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 50); }, []); // Day-5 will swap this stub for LeadCaptureModal const handleLeadCapture = React.useCallback(() => { const subject = encodeURIComponent('LexiPanel for Defense — 15-min feedback'); const body = encodeURIComponent('Hi Yaroslav,\n\nSaw the LexiPanel for Defense demo at the ShowCases. Would love to talk.\n\n— '); window.location.href = `mailto:y.baienko@golaw.ua?subject=${subject}&body=${body}`; }, []); const version = (typeof window !== 'undefined' && window.__MILTECH_BUILD__) || 'dev'; return (