wasrusgen1-crm/Mokap/cabinet.html
2026-05-30 16:09:10 +03:00

537 lines
47 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Кабинет клиента · @wasrusgen1 | КОНСАЛТИНГ</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Montserrat:wght@700;800&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{--primary:#047857;--dark:#064E3B;--mid:#10B981;--light:#ECFDF5;--sb:#0F0F1A;--bg:#F5F6F8;--white:#fff;--border:#E5E7EB;--text:#1A1A2E;--muted:#6B7280;--subtle:#F9FAFB}
html,body{height:100%;overflow:hidden}
body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);display:flex;flex-direction:column}
/* Header */
.hdr{height:54px;background:var(--dark);display:flex;align-items:center;padding:0 20px;gap:12px;flex-shrink:0}
.hdr-ic{width:30px;height:30px;background:var(--primary);border-radius:8px;display:flex;align-items:center;justify-content:center;font-family:'Montserrat';font-weight:800;font-size:16px;color:#fff}
.hdr-t{font-family:'Montserrat';font-weight:700;font-size:14px;color:rgba(255,255,255,.6);display:flex;align-items:center;gap:9px;letter-spacing:-.2px}
.hdr-sep{width:1.5px;height:15px;background:rgba(255,255,255,.25);flex-shrink:0}
.hdr-t b{font-weight:800;color:#fff}
.hdr-client{font-size:13px;color:rgba(255,255,255,.6);margin-left:6px}
.hdr-r{margin-left:auto}
.elena-chip{display:flex;align-items:center;gap:7px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);border-radius:100px;padding:5px 13px 5px 6px}
.elena-av{width:24px;height:24px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;font-family:'Montserrat';font-weight:800;font-size:11px;color:#fff}
.elena-nm{font-size:12px;font-weight:600;color:rgba(255,255,255,.85)}
.elena-dot{width:7px;height:7px;background:var(--mid);border-radius:50%;box-shadow:0 0 4px var(--mid)}
/* Layout */
.layout{flex:1;display:flex;overflow:hidden}
.sb{width:240px;flex-shrink:0;background:var(--sb);display:flex;flex-direction:column}
.sb-nav{flex:1;padding:14px 0;overflow-y:auto}
.sb-cap{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:rgba(255,255,255,.2);padding:10px 18px 6px}
.si{display:flex;align-items:flex-start;gap:11px;padding:10px 18px;cursor:pointer;border-left:2px solid transparent;transition:background .12s}
.si:hover:not(.locked){background:rgba(255,255,255,.04)}
.si.active{background:rgba(4,120,87,.14);border-left-color:var(--primary)}
.si.locked{opacity:.4;cursor:default}
.si-num{width:28px;height:28px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}
.si.done .si-num{background:rgba(16,185,129,.15);color:var(--mid);font-size:14px}
.si.active .si-num{background:var(--primary);color:#fff;box-shadow:0 4px 10px rgba(4,120,87,.4)}
.si:not(.active):not(.done) .si-num{background:rgba(255,255,255,.06);color:rgba(255,255,255,.4)}
.si.locked .si-num{background:rgba(255,255,255,.05);color:rgba(255,255,255,.2);font-size:11px}
.si-body{flex:1;min-width:0}
.si-lbl{font-size:10px;color:rgba(255,255,255,.25);font-weight:600;letter-spacing:.04em;text-transform:uppercase}
.si-name{font-size:13px;font-weight:600;color:rgba(255,255,255,.7);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.si.active .si-name{color:#fff}
.si-sub{font-size:11px;color:rgba(255,255,255,.3);margin-top:1px}
.sb-foot{padding:14px;border-top:1px solid rgba(255,255,255,.06)}
.pb-box{background:rgba(255,255,255,.04);border-radius:10px;padding:12px}
.pb-row{display:flex;justify-content:space-between;margin-bottom:6px}
.pb-l{font-size:12px;font-weight:600;color:rgba(255,255,255,.45)}
.pb-p{font-size:12px;font-weight:700;color:var(--mid)}
.pb-track{height:4px;background:rgba(255,255,255,.08);border-radius:2px;overflow:hidden}
.pb-fill{height:100%;background:linear-gradient(90deg,var(--primary),var(--mid));border-radius:2px;transition:width .4s}
/* Main */
.main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
.sv{display:none;flex:1;flex-direction:column;overflow:hidden}
.sv.active{display:flex}
.hero{background:var(--white);border-bottom:1px solid var(--border);padding:18px 26px;display:flex;align-items:center;gap:16px;flex-shrink:0}
.hero-ic{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,var(--dark),var(--primary));display:flex;align-items:center;justify-content:center;font-size:21px;flex-shrink:0}
.hero-tag{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);margin-bottom:3px}
.hero-h{font-size:18px;font-weight:700;color:var(--text)}
.hero-d{font-size:13px;color:var(--muted);margin-top:2px}
.hero-r{margin-left:auto}
.btn{font-family:'Inter';font-weight:600;border:none;cursor:pointer;border-radius:10px;display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
.btn-p{background:var(--primary);color:#fff;font-size:13px;padding:10px 18px}
.btn-p:hover{background:var(--dark)}.btn-p:disabled{opacity:.5;cursor:default}
.scroll{flex:1;overflow-y:auto}
/* Chat */
.chat{padding:24px 26px;display:flex;flex-direction:column;gap:14px}
.msg{display:flex;gap:10px;max-width:80%}
.msg.user{align-self:flex-end;flex-direction:row-reverse}
.av{width:30px;height:30px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#fff}
.av.e{background:var(--primary)}.av.u{background:#6366F1}
.bb{padding:11px 15px;border-radius:14px;font-size:14px;line-height:1.55;white-space:pre-wrap}
.bb.in{background:var(--white);border:1px solid var(--border);border-top-left-radius:4px}
.bb.out{background:var(--primary);color:#fff;border-top-right-radius:4px}
.typing{display:flex;gap:4px;padding:13px 16px;background:var(--white);border:1px solid var(--border);border-radius:14px;border-top-left-radius:4px;width:fit-content}
.typing span{width:7px;height:7px;border-radius:50%;background:#cbd5e1;animation:b 1.2s infinite}
.typing span:nth-child(2){animation-delay:.2s}.typing span:nth-child(3){animation-delay:.4s}
@keyframes b{0%,80%,100%{transform:scale(.7);opacity:.4}40%{transform:scale(1);opacity:1}}
.inbar{padding:14px 26px;border-top:1px solid var(--border);background:var(--white);display:flex;gap:10px;align-items:flex-end;flex-shrink:0}
.inp{flex:1;border:1.5px solid var(--border);border-radius:12px;padding:11px 15px;font-size:14px;font-family:'Inter';resize:none;outline:none;max-height:120px;line-height:1.5}
.inp:focus{border-color:var(--mid);box-shadow:0 0 0 3px rgba(16,185,129,.1)}
.icon-btn{width:44px;height:44px;border-radius:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative}
.mic{background:var(--light);border:1.5px solid rgba(4,120,87,.2);color:var(--primary)}
.mic:hover{background:#D1FAE5}
.mic.rec{background:#FEF2F2;border-color:#EF4444;color:#EF4444}
.mic.rec::after{content:'';position:absolute;inset:-4px;border-radius:14px;border:2px solid #EF4444;animation:mp 1.3s ease-out infinite}
@keyframes mp{0%{transform:scale(1);opacity:.7}100%{transform:scale(1.25);opacity:0}}
.send{background:var(--primary);border:none;color:#fff}.send:hover{background:var(--dark)}.send:disabled{opacity:.4}
.mic-hint{position:absolute;bottom:56px;right:0;background:var(--sb);color:#fff;font-size:12px;padding:6px 12px;border-radius:8px;white-space:nowrap;display:none}
.mic-hint.show{display:block}
/* Analysis panes */
.pad{padding:24px 26px}
.run-card{background:var(--white);border:1.5px solid var(--border);border-radius:14px;padding:28px;text-align:center;max-width:520px;margin:8px auto 16px}
.run-ic{font-size:34px;margin-bottom:12px}
.run-t{font-family:'Montserrat';font-weight:800;font-size:19px;color:var(--text);margin-bottom:8px}
.run-d{font-size:14px;color:var(--muted);line-height:1.5;margin-bottom:18px}
.an-tabs{display:flex;gap:8px;margin-bottom:18px}
.an-tab{padding:9px 16px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer;background:var(--white);border:1.5px solid var(--border);color:var(--muted)}
.an-tab.active{background:var(--primary);border-color:var(--primary);color:#fff}
.an-pane{display:none}.an-pane.active{display:block}
/* Canvas */
.canvas-grid{display:grid;grid-template-columns:repeat(5,1fr);grid-auto-rows:minmax(84px,auto);gap:8px}
.cv{background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px}
.cv-h{font-size:10px;font-weight:700;text-transform:uppercase;color:var(--primary);margin-bottom:6px;display:flex;justify-content:space-between}
.cv-h span{color:#9ca3af}
.cv li{font-size:11.5px;color:#374151;line-height:1.35;list-style:none;padding-left:10px;position:relative;margin-bottom:3px}
.cv li::before{content:'';position:absolute;left:2px;top:7px;width:4px;height:4px;border-radius:50%;background:var(--mid)}
.cv-note{font-size:10px;color:#92400e;margin-top:6px;font-style:italic}
.cv.vp{grid-column:3;grid-row:span 2;border-color:var(--primary);border-width:1.5px}.cv.kp{grid-row:span 2}.cv.cs{grid-column:5;grid-row:span 2}
.cv-ins{grid-column:1/-1;background:var(--sb);color:#fff;border-radius:11px;padding:14px 18px;font-size:13px;line-height:1.5}
.cv-ins b{color:var(--mid)}
/* IDEF0 */
.idef-lbl{font-size:11px;font-weight:700;text-transform:uppercase;color:#9ca3af;margin:14px 0 8px}
.idef{margin-bottom:12px}
.idef-c,.idef-m{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;padding:4px 0}
.idef-c{border-bottom:2px dashed #cbd5e1}.idef-m{border-top:2px dashed #cbd5e1}
.idef-mid{display:grid;grid-template-columns:auto 1fr auto;gap:8px;align-items:stretch}
.idef-i,.idef-o{display:flex;flex-direction:column;gap:4px;justify-content:center;max-width:140px}
.idef-fn{background:var(--white);border:2px solid var(--primary);border-radius:10px;padding:11px 9px;text-align:center;font-size:12.5px;font-weight:700;color:var(--dark);display:flex;flex-direction:column;gap:3px;justify-content:center}
.idef-fn b{font-size:10px;color:var(--primary);font-family:'Montserrat'}.idef-fn i{font-style:normal;font-size:10px;font-weight:700}
.ar{font-size:10px;line-height:1.3;padding:3px 7px;border-radius:6px;background:#F1F5F9;color:#475569}
.idef-c .ar{background:#FEF3C7;color:#92400E}.idef-c .ar.nomiss{background:#FEF2F2;color:#DC2626;border:1px dashed #FECACA}
.idef-i .ar{background:#EFF6FF;color:#1E40AF}.idef-o .ar{background:#ECFDF5;color:#047857}.idef-o .ar.dead{background:#FEF2F2;color:#DC2626}
.idef-m .ar{background:#F5F3FF;color:#6D28D9}
.blk{background:var(--white);border:1px solid var(--border);border-radius:12px;padding:14px;margin-bottom:10px}
.blk-pain{font-size:12px;color:#92400e;padding-left:14px;position:relative;margin-bottom:3px;line-height:1.4}
.blk-pain::before{content:'•';position:absolute;left:3px;color:#f59e0b}
/* Spec */
.spec-h{font-family:'Montserrat';font-weight:800;font-size:16px;color:var(--text);margin:18px 0 12px;display:flex;align-items:center;gap:8px}
.spec-h .pl{width:24px;height:24px;border-radius:7px;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800}
.mod{background:var(--white);border:1px solid var(--border);border-radius:12px;padding:15px;margin-bottom:10px}
.mod-top{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.mod-node{font-size:10px;font-weight:700;color:var(--primary);background:var(--light);padding:2px 7px;border-radius:5px}
.mod-name{font-size:14px;font-weight:700}
.scr{font-size:11px;background:#EFF6FF;color:#1E40AF;border:1px solid #BFDBFE;border-radius:6px;padding:3px 9px;display:inline-block;margin:3px 3px 0 0}
.mod-data{font-size:12px;color:#374151;margin-top:6px}
.ent{background:var(--white);border:1px solid var(--border);border-radius:11px;padding:13px;margin-bottom:8px}
.ent-name{font-size:13px;font-weight:700;font-family:'Montserrat';margin-bottom:8px}
.ent-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:5px;margin-bottom:8px}
.fld{font-size:11px;background:var(--bg);border-radius:6px;padding:4px 8px}.fld em{font-style:normal;color:#6366F1;font-size:10px}
.ent-ex{font-size:10.5px;color:#6b7280;font-family:monospace;background:var(--bg);border-radius:6px;padding:6px 9px;line-height:1.4}
/* Docs */
.pf-l{display:block;font-size:13px;font-weight:600;color:var(--text);margin-bottom:7px}
.pf-i{width:100%;border:1.5px solid var(--border);border-radius:10px;padding:11px 14px;font-size:14px;font-family:'Inter';outline:none}
.pf-i:focus{border-color:var(--mid);box-shadow:0 0 0 3px rgba(16,185,129,.1)}
.pf-t{width:100%;min-height:120px;border:1.5px solid var(--border);border-radius:10px;padding:11px 14px;font-size:14px;font-family:'Inter';outline:none;resize:vertical;line-height:1.5}
.pf-t:focus{border-color:var(--mid);box-shadow:0 0 0 3px rgba(16,185,129,.1)}
.drop{border:2px dashed #CBD5E1;border-radius:14px;padding:32px;text-align:center;background:var(--subtle);max-width:520px;margin:8px auto}
.drop-ic{font-size:30px;margin-bottom:8px}
.spin{display:inline-block;animation:sp 1s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}
.pay-banner{display:none;background:linear-gradient(135deg,var(--dark),var(--primary));color:#fff;padding:12px 26px;align-items:center;gap:14px;flex-shrink:0}
.pay-banner.show{display:flex}
.pay-banner b{font-size:16px;font-family:Montserrat}
.pay-btn{margin-left:auto;background:#fff;color:var(--primary);border:none;border-radius:9px;padding:9px 20px;font-weight:700;font-size:13px;cursor:pointer;font-family:Inter}
.modal-bg{display:none;position:fixed;inset:0;background:rgba(15,15,26,.6);z-index:100;align-items:center;justify-content:center}
.modal-bg.show{display:flex}
.modal{background:#fff;border-radius:16px;padding:26px;max-width:440px;width:90%}
.modal-h{font-family:Montserrat;font-weight:800;font-size:20px;margin-bottom:4px}
.modal-sub{font-size:14px;color:var(--muted);margin-bottom:20px}
.pay-opt{display:flex;align-items:center;gap:14px;border:1.5px solid var(--border);border-radius:12px;padding:15px;margin-bottom:10px;cursor:pointer;transition:all .15s}
.pay-opt:hover{border-color:var(--primary);background:var(--light)}
.pay-opt-ic{width:42px;height:42px;border-radius:10px;background:var(--light);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.pay-opt-t{font-size:15px;font-weight:700}.pay-opt-d{font-size:12px;color:var(--muted)}
.modal-close{margin-top:8px;width:100%;background:transparent;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:8px}
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:rgba(0,0,0,.12);border-radius:4px}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-ic">@</div>
<div class="hdr-t">wasrusgen1<span class="hdr-sep"></span><b>КОНСАЛТИНГ</b></div>
<div class="hdr-client" id="hdrClient"></div>
<div class="hdr-r"><div class="elena-chip"><div class="elena-av">Е</div><div class="elena-nm">Елена</div><div class="elena-dot"></div></div></div>
</header>
<div class="pay-banner" id="payBanner"><span>💳 К оплате: <b id="payAmount"></b></span><button class="pay-btn" onclick="showPayModal()">Оплатить →</button></div>
<div class="modal-bg" id="signModal">
<div class="modal" style="max-width:520px">
<div class="modal-h">Подписание договора</div>
<div class="modal-sub">Для продолжения подпишите документы простой электронной подписью</div>
<div style="background:var(--subtle);border:1.5px solid var(--border);border-radius:11px;padding:14px;margin-bottom:16px;font-size:13px;line-height:1.6">
Ознакомьтесь с документами:<br>
📄 <a href="#" onclick="viewDoc('offer');return false" style="color:var(--primary);font-weight:600">Договор-оферта на консультационные услуги</a><br>
📄 <a href="#" onclick="viewDoc('pep');return false" style="color:var(--primary);font-weight:600">Соглашение об использовании ПЭП</a>
</div>
<label style="display:flex;align-items:flex-start;gap:10px;margin-bottom:16px;cursor:pointer">
<input type="checkbox" id="signAgree" style="width:18px;height:18px;margin-top:2px;flex-shrink:0">
<span style="font-size:13px;line-height:1.5">Я ознакомлен и принимаю условия Договора-оферты и Соглашения об использовании простой электронной подписи</span>
</label>
<div id="signStep1">
<input id="signId" placeholder="Телефон или email" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:11px 14px;font-size:14px;font-family:Inter;outline:none;margin-bottom:12px">
<button class="btn btn-p" style="width:100%;justify-content:center" onclick="signRequest()">Получить код подтверждения</button>
</div>
<div id="signStep2" style="display:none">
<div id="signCodeHint" style="font-size:12px;color:var(--primary);background:var(--light);border-radius:8px;padding:8px 12px;margin-bottom:12px"></div>
<input id="signCode" placeholder="Код из SMS/email" maxlength="4" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:11px 14px;font-size:18px;font-family:Inter;outline:none;margin-bottom:12px;text-align:center;letter-spacing:8px">
<button class="btn btn-p" style="width:100%;justify-content:center" onclick="signConfirm()">✓ Подписать договор</button>
</div>
<button class="modal-close" onclick="document.getElementById('signModal').classList.remove('show')">Отмена</button>
</div>
</div>
<div class="modal-bg" id="docModal">
<div class="modal" style="max-width:720px;max-height:80vh;display:flex;flex-direction:column">
<div class="modal-h" id="docTitle" style="flex-shrink:0">Документ</div>
<div id="docText" style="flex:1;overflow-y:auto;font-size:12.5px;line-height:1.6;white-space:pre-wrap;color:#374151;margin:12px 0;padding-right:8px"></div>
<button class="btn btn-p" style="flex-shrink:0;justify-content:center" onclick="document.getElementById('docModal').classList.remove('show')">Закрыть</button>
</div>
</div>
<div class="modal-bg" id="payModal">
<div class="modal">
<div class="modal-h">Оплата консалтинга</div>
<div class="modal-sub">Сумма: <b id="modalAmount"></b> · выберите способ</div>
<div class="pay-opt" onclick="payVia('card')"><div class="pay-opt-ic">💳</div><div><div class="pay-opt-t">Банковская карта</div><div class="pay-opt-d">Visa, Mastercard, Мир · мгновенно</div></div></div>
<div class="pay-opt" onclick="payVia('sbp')"><div class="pay-opt-ic">📱</div><div><div class="pay-opt-t">СБП — по QR-коду</div><div class="pay-opt-d">Перевод через приложение банка</div></div></div>
<div class="pay-opt" onclick="payVia('cash')"><div class="pay-opt-ic">💵</div><div><div class="pay-opt-t">Наличными</div><div class="pay-opt-d">При встрече с консультантом</div></div></div>
<button class="modal-close" onclick="document.getElementById('payModal').classList.remove('show')">Отмена</button>
</div>
</div>
<div class="layout">
<aside class="sb">
<div class="sb-nav">
<div class="si" id="si0" onclick="go(0)" style="margin-bottom:4px"><div class="si-num" style="background:rgba(255,255,255,.1);color:rgba(255,255,255,.7)">👤</div><div class="si-body"><div class="si-name">Профиль компании</div><div class="si-sub">О вашей деятельности</div></div></div>
<div style="height:1px;background:rgba(255,255,255,.06);margin:4px 18px 8px"></div>
<div class="sb-cap">Путь к результату</div>
<div class="si active" id="si1" onclick="go(1)"><div class="si-num">1</div><div class="si-body"><div class="si-lbl">Этап 1</div><div class="si-name">Знакомство</div><div class="si-sub">Интервью с Еленой</div></div></div>
<div class="si" id="si2" onclick="go(2)"><div class="si-num">2</div><div class="si-body"><div class="si-lbl">Этап 2</div><div class="si-name">Диагностика</div><div class="si-sub">Углубление</div></div></div>
<div class="si" id="si3" onclick="go(3)"><div class="si-num">3</div><div class="si-body"><div class="si-lbl">Этап 3</div><div class="si-name">Документы</div><div class="si-sub">Материалы</div></div></div>
<div class="si" id="si4" onclick="go(4)"><div class="si-num">4</div><div class="si-body"><div class="si-lbl">Этап 4</div><div class="si-name">Анализ</div><div class="si-sub">Модель бизнеса</div></div></div>
<div class="si" id="si5" onclick="go(5)"><div class="si-num">5</div><div class="si-body"><div class="si-lbl">Этап 5</div><div class="si-name">План</div><div class="si-sub">ТЗ на программу</div></div></div>
</div>
<div class="sb-foot"><div class="pb-box"><div class="pb-row"><span class="pb-l">Прогресс</span><span class="pb-p" id="pbPct">20%</span></div><div class="pb-track"><div class="pb-fill" id="pbFill" style="width:20%"></div></div></div></div>
</aside>
<main class="main">
<!-- Профиль компании -->
<div class="sv" id="sv0">
<div class="hero"><div class="hero-ic">👤</div><div><div class="hero-tag">Профиль компании</div><div class="hero-h">Расскажите о вашей деятельности</div><div class="hero-d">Базовая информация — Елена начнёт разговор уже зная контекст</div></div></div>
<div class="scroll"><div class="pad">
<div style="max-width:600px">
<div style="margin-bottom:18px"><label class="pf-l">Ваше имя или название компании</label><input class="pf-i" id="pfName" placeholder="Например: Игорь / ООО «Автодом»"></div>
<div style="margin-bottom:18px"><label class="pf-l">Сфера деятельности</label><input class="pf-i" id="pfNiche" placeholder="Например: автосервис, нутрициология, швейное производство"></div>
<div style="margin-bottom:22px"><label class="pf-l">Описание деятельности</label><textarea class="pf-t" id="pfDesc" placeholder="Чем занимаетесь, сколько человек, как сейчас всё устроено, что беспокоит. Чем подробнее — тем точнее анализ."></textarea></div>
<button class="btn btn-p" id="pfSave" onclick="saveProfile()" style="padding:12px 24px;font-size:14px">Сохранить и начать с Еленой →</button>
</div>
</div></div>
</div>
<!-- Этап 1 — Знакомство (живой чат) -->
<div class="sv active" id="sv1">
<div class="hero"><div class="hero-ic">💬</div><div><div class="hero-tag">Этап 1 из 5 · В процессе</div><div class="hero-h">Знакомство</div><div class="hero-d">Расскажите Елене о вашем бизнесе — текстом или голосом</div></div></div>
<div class="scroll"><div class="chat" id="chat"></div></div>
<div class="inbar">
<textarea class="inp" id="inp" rows="1" placeholder="Напишите или скажите Елене..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
<button class="icon-btn mic" id="micBtn" onclick="toggleMic()" title="Голосовой ввод"><div class="mic-hint" id="micHint">Слушаю... говорите</div><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></button>
<button class="icon-btn send" id="sendBtn" onclick="sendMsg()"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg></button>
</div>
</div>
<!-- Этап 2 — Диагностика -->
<div class="sv" id="sv2">
<div class="hero"><div class="hero-ic">🔍</div><div><div class="hero-tag">Этап 2 из 5</div><div class="hero-h">Диагностика</div><div class="hero-d">Елена уточняет детали — продолжайте разговор</div></div></div>
<div class="scroll"><div class="pad"><div class="run-card"><div class="run-ic">🔍</div><div class="run-t">Диагностика идёт в чате</div><div class="run-d">Елена задаёт уточняющие вопросы прямо в интервью (Этап 1). Когда данных достаточно — переходите к Анализу.</div><button class="btn btn-p" onclick="go(1)">← Вернуться к интервью</button></div></div></div>
</div>
<!-- Этап 3 — Документы -->
<div class="sv" id="sv3">
<div class="hero"><div class="hero-ic">📁</div><div><div class="hero-tag">Этап 3 из 5</div><div class="hero-h">Документы</div><div class="hero-d">Загрузите материалы — Елена учтёт их в анализе</div></div></div>
<div class="scroll"><div class="pad">
<div class="drop" id="dropZone" onclick="document.getElementById('fileInp').click()">
<div class="drop-ic">📎</div>
<div style="font-size:14px;font-weight:600;margin-bottom:4px">Нажмите или перетащите документы</div>
<div style="font-size:12px;color:#9ca3af">PDF, Word, Excel, txt · прайс, оргструктура, отчёты</div>
<input type="file" id="fileInp" multiple style="display:none" onchange="handleFiles(this.files)">
</div>
<div id="docList" style="max-width:520px;margin:16px auto 0"></div>
</div></div>
</div>
<!-- Этап 4 — Анализ -->
<div class="sv" id="sv4">
<div class="hero"><div class="hero-ic">🧠</div><div><div class="hero-tag">Этап 4 из 5</div><div class="hero-h">Анализ бизнеса</div><div class="hero-d">Елена строит модель вашего бизнеса</div></div></div>
<div class="scroll"><div class="pad" id="anPad"></div></div>
</div>
<!-- Этап 5 — План -->
<div class="sv" id="sv5">
<div class="hero"><div class="hero-ic">🚀</div><div><div class="hero-tag">Этап 5 из 5</div><div class="hero-h">План — ТЗ на программу</div><div class="hero-d">Проект системы для вашего бизнеса</div></div></div>
<div class="scroll"><div class="pad" id="specPad"></div></div>
</div>
</main>
</div>
<script>
const API=location.hostname.indexOf("wasrusgen1.ru")>=0?"/consulting":"https://claude-83-172-150-111.sslip.io/elena";
// Доступ: токен из ссылки-приглашения (?t=) имеет приоритет
const urlToken=new URLSearchParams(location.search).get("t");
if(urlToken)localStorage.setItem("cab_token",urlToken);
let token=urlToken||localStorage.getItem("cab_token"), state=null, cur=1;
const chat=document.getElementById("chat"), inp=document.getElementById("inp");
const PCTS={1:20,2:40,3:60,4:80,5:100};
function esc(s){return (s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
function fmt(s){return esc(s).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>")}
function go(n){
if(document.getElementById('si'+n).classList.contains('locked'))return;
cur=n;
document.querySelectorAll('.sv').forEach(s=>s.classList.remove('active'));
document.getElementById('sv'+n).classList.add('active');
document.querySelectorAll('.si').forEach(s=>s.classList.remove('active'));
document.getElementById('si'+n).classList.add('active');
if(PCTS[n]!=null){document.getElementById('pbPct').textContent=PCTS[n]+'%';document.getElementById('pbFill').style.width=PCTS[n]+'%';}
if(n===3)renderDocs();
if(n===4)renderAnalysis();
if(n===5)renderSpecPane();
}
async function saveProfile(){
const name=document.getElementById("pfName").value.trim();
const niche=document.getElementById("pfNiche").value.trim();
const desc=document.getElementById("pfDesc").value.trim();
if(!name&&!desc){alert("Заполните хотя бы имя и описание");return}
const btn=document.getElementById("pfSave");btn.disabled=true;btn.innerHTML='<span class="spin">⏳</span> Елена знакомится...';
try{
const r=await fetch(`${API}/api/project/profile`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,client_name:name,niche,description:desc})});
const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);btn.disabled=false;btn.textContent="Сохранить и начать →";return}
// Обновляем состояние и чат
const r2=await fetch(`${API}/api/project/${token}`);state=await r2.json();
renderAll();go(1);
}catch(e){alert("Ошибка: "+e.message);btn.disabled=false}
}
/* ── Voice ── */
let recog=null,recording=false;
const SR=window.SpeechRecognition||window.webkitSpeechRecognition;
if(SR){recog=new SR();recog.lang="ru-RU";recog.continuous=true;recog.interimResults=true;let base="";
recog.onresult=e=>{let fin="",intr="";for(let i=e.resultIndex;i<e.results.length;i++){const t=e.results[i][0].transcript;if(e.results[i].isFinal)fin+=t;else intr+=t}if(fin)base+=fin;inp.value=(base+intr).trim();inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"};
recog.onstart=()=>{base=inp.value?inp.value+" ":""};
recog.onend=()=>{if(recording){try{recog.start()}catch(e){}}};
recog.onerror=e=>{if(e.error==="not-allowed"){alert("Разрешите доступ к микрофону");stopMic()}};
}
function toggleMic(){if(!recog){alert("Голосовой ввод работает в Chrome");return}recording?stopMic():startMic()}
function startMic(){recording=true;document.getElementById("micBtn").classList.add("rec");document.getElementById("micHint").classList.add("show");try{recog.start()}catch(e){}}
function stopMic(){recording=false;document.getElementById("micBtn").classList.remove("rec");document.getElementById("micHint").classList.remove("show");try{recog.stop()}catch(e){}inp.focus()}
/* ── Chat ── */
function addMsg(role,text){const m=document.createElement("div");m.className="msg "+(role==="user"?"user":"");m.innerHTML=`<div class="av ${role==='user'?'u':'e'}">${role==='user'?'Я':'Е'}</div><div class="bb ${role==='user'?'out':'in'}">${fmt(text)}</div>`;chat.appendChild(m);chat.scrollTop=chat.scrollHeight}
function showTyping(){const t=document.createElement("div");t.className="msg";t.id="typing";t.innerHTML=`<div class="av e">Е</div><div class="typing"><span></span><span></span><span></span></div>`;chat.appendChild(t);chat.scrollTop=chat.scrollHeight}
function hideTyping(){const t=document.getElementById("typing");if(t)t.remove()}
async function init(){
if(token){const r=await fetch(`${API}/api/project/${token}`);if(r.ok){state=await r.json();renderAll();
fillProfile();
// Если профиль не заполнен — открыть вкладку Профиль
if(!state.description && !state.client_name) go(0); else go(1);
return;}}
const r=await fetch(`${API}/api/project/new`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})});
const d=await r.json();token=d.token;localStorage.setItem("cab_token",token);
state={messages:[],client_name:"",niche:"",description:""};
go(0); // новый клиент → сразу профиль
}
function fillProfile(){
if(state.client_name)document.getElementById("pfName").value=state.client_name;
if(state.niche)document.getElementById("pfNiche").value=state.niche;
if(state.description)document.getElementById("pfDesc").value=state.description;
}
/* ── Документы (Этап 3) ── */
function renderDocs(){
const dl=document.getElementById("docList");if(!dl)return;
const docs=state.documents||[];
dl.innerHTML=docs.length?docs.map(d=>`<div style="display:flex;align-items:center;gap:10px;background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px 14px;margin-bottom:8px"><span style="font-size:18px">📄</span><div style="flex:1"><div style="font-size:13px;font-weight:600">${esc(d.filename)}</div><div style="font-size:11px;color:#9ca3af">${(d.size/1024).toFixed(0)} КБ · учтён в анализе</div></div><span style="color:var(--primary);font-weight:700;font-size:13px">✓</span></div>`).join(""):'<div style="text-align:center;color:#cbd5e1;font-size:13px;padding:10px">Документов пока нет</div>';
}
async function handleFiles(files){
const dl=document.getElementById("docList");
for(const f of files){
const tmp=document.createElement("div");tmp.style.cssText="background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px 14px;margin-bottom:8px;font-size:13px";tmp.innerHTML=`<span class="spin">⏳</span> ${esc(f.name)} — загрузка...`;dl.prepend(tmp);
try{
const b64=await new Promise((res,rej)=>{const r=new FileReader();r.onload=()=>res(r.result);r.onerror=rej;r.readAsDataURL(f)});
const r=await fetch(`${API}/api/upload`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,filename:f.name,content:b64})});
const d=await r.json();
if(d.error){tmp.innerHTML=`${esc(f.name)}: ${d.error}`;continue}
state.documents=state.documents||[];state.documents.push({filename:d.filename,size:d.size});
}catch(e){tmp.innerHTML=`${esc(f.name)}: ${e.message}`;continue}
}
renderDocs();
}
function renderAll(){
if(state.client_name)document.getElementById("hdrClient").textContent="· "+state.client_name;
chat.innerHTML="";
state.messages.forEach(m=>addMsg(m.role==="user"?"user":"elena",m.content));
unlockStages();
checkPayment();
}
function money(n){return (n||0).toLocaleString("ru-RU")+" ₽"}
function checkPayment(){
const crm=state.crm||{};const deal=crm.deal_amount||0;
const paid=(crm.payments||[]).reduce((s,p)=>s+(p.amount||0),0);
const left=deal-paid;
if(left>0){
document.getElementById("payAmount").textContent=money(left);
document.getElementById("modalAmount").textContent=money(left);
document.getElementById("payBanner").classList.add("show");
const btn=document.querySelector("#payBanner .pay-btn");
if(btn)btn.textContent=state.signed?"Оплатить →":"Подписать и оплатить →";
window.__payLeft=left;
}else document.getElementById("payBanner").classList.remove("show");
}
function showPayModal(){
// Оплата только после подписания договора
if(!state.signed){document.getElementById("signModal").classList.add("show");return}
document.getElementById("payModal").classList.add("show");
}
async function viewDoc(doc){
const dm=document.getElementById("docModal");
document.getElementById("docTitle").textContent="Загрузка...";document.getElementById("docText").textContent="";dm.classList.add("show");
try{const r=await fetch(`${API}/api/legal/${doc}`);const d=await r.json();
document.getElementById("docTitle").textContent=doc==="offer"?"Договор-оферта":"Соглашение об использовании ПЭП";
document.getElementById("docText").textContent=d.text||d.error||"Документ недоступен";
}catch(e){document.getElementById("docText").textContent="Ошибка: "+e.message}
}
async function signRequest(){
if(!document.getElementById("signAgree").checked){alert("Поставьте отметку о согласии с условиями");return}
const id=document.getElementById("signId").value.trim();
if(!id){alert("Укажите телефон или email");return}
try{const r=await fetch(`${API}/api/sign/request`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,identifier:id})});const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);return}
document.getElementById("signStep1").style.display="none";
document.getElementById("signStep2").style.display="block";
document.getElementById("signCodeHint").textContent=d.demo_code?("ДЕМО-код: "+d.demo_code+" (в проде придёт по SMS/email)"):"Код отправлен на "+id;
}catch(e){alert("Ошибка: "+e.message)}
}
async function signConfirm(){
const code=document.getElementById("signCode").value.trim();
if(code.length<4){alert("Введите код");return}
try{const r=await fetch(`${API}/api/sign/confirm`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,code,docs:["offer","pep"]})});const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);return}
state.signed=true;
document.getElementById("signModal").classList.remove("show");
document.getElementById("signStep1").style.display="block";document.getElementById("signStep2").style.display="none";
alert("✓ Договор подписан\nПодписант: "+d.identifier);
document.getElementById("payModal").classList.add("show"); // сразу к оплате
}catch(e){alert("Ошибка: "+e.message)}
}
async function payVia(method){
const amount=window.__payLeft||0;
document.getElementById("payModal").classList.remove("show");
try{
const r=await fetch(`${API}/api/payment/create`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,amount,method,return_url:location.href})});
const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);return}
if(method==="cash"){alert("💵 "+d.instructions);return}
if(d.demo){alert("ДЕМО-режим: ЮKassa ещё не подключена.\n\n"+d.note);return}
if(d.qr){alert("СБП: отсканируйте QR в приложении банка\n\n"+(d.qr||""));return}
if(d.confirmation_url){location.href=d.confirmation_url;return}
}catch(e){alert("Ошибка: "+e.message)}
}
function unlockStages(){
// Этап 4 доступен если есть достаточно сообщений
const enough=state.messages.length>=3;
[2,3,4,5].forEach(n=>{const si=document.getElementById('si'+n);if(enough||n<=3)si.classList.remove('locked');});
}
async function sendMsg(){
if(recording)stopMic();
const text=inp.value.trim();if(!text)return;
inp.value="";inp.style.height="auto";
addMsg("user",text);state.messages.push({role:"user",content:text});
document.getElementById("sendBtn").disabled=true;showTyping();
try{const r=await fetch(`${API}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,message:text})});const d=await r.json();hideTyping();
addMsg("elena",d.reply||("Ошибка: "+(d.error||"?")));state.messages.push({role:"assistant",content:d.reply||""});unlockStages();
}catch(e){hideTyping();addMsg("elena","Ошибка связи: "+e.message)}
document.getElementById("sendBtn").disabled=false;
}
/* ── Analysis (Этап 4) ── */
let anTab="canvas";
function renderAnalysis(){
const pad=document.getElementById("anPad");
pad.innerHTML=`<div class="an-tabs"><div class="an-tab ${anTab==='canvas'?'active':''}" onclick="setAnTab('canvas')">📊 Стратегия</div><div class="an-tab ${anTab==='idef0'?'active':''}" onclick="setAnTab('idef0')">🔧 Функции</div></div><div id="anContent"></div>`;
renderAnContent();
}
function setAnTab(t){anTab=t;renderAnalysis()}
function renderAnContent(){
const c=document.getElementById("anContent");
if(anTab==='canvas'){
if(!state.canvas){c.innerHTML=runCard("canvas","📊","Стратегическая модель","Елена построит Business Model Canvas — как устроен ваш бизнес и как он зарабатывает.","Построить стратегию →");return}
c.innerHTML=renderCanvas(state.canvas);
}else{
if(!state.model){c.innerHTML=runCard("model","🔧","Функциональная модель","Елена разложит бизнес на функции (IDEF0): входы, выходы, нормы, ресурсы и разрывы.","Построить модель →");return}
c.innerHTML=renderIdef(state.model);
}
}
function renderSpecPane(){
const pad=document.getElementById("specPad");
if(!state.spec){
if(!state.model){pad.innerHTML=runCard(null,"⚠️","Сначала Анализ","ТЗ собирается из функциональной модели. Перейдите на Этап 4 и постройте модель.","← К анализу",()=>go(4));return}
pad.innerHTML=runCard("spec","📋","Техническое задание","Из модели бизнеса Елена спроектирует программу: роли, модули, экраны, данные.","Собрать ТЗ →");return}
pad.innerHTML=renderSpec(state.spec);
}
function runCard(stage,ic,t,d,btn,custom){
const id=stage?`id="rb-${stage}"`:'';
const onclick=custom?'':`onclick="runBuild('${stage}')"`;
return `<div class="run-card"><div class="run-ic">${ic}</div><div class="run-t">${t}</div><div class="run-d">${d}</div><button class="btn btn-p" ${id} ${custom?'':onclick}>${btn}</button></div>`;
}
const BUILD={canvas:["build-canvas","canvas"],model:["build-model","model"],spec:["build-spec","spec"]};
async function runBuild(stage){
const [ep,key]=BUILD[stage];const btn=document.getElementById(`rb-${stage}`);
if(btn){btn.disabled=true;btn.innerHTML=`<span class="spin">⏳</span> Елена анализирует...`}
try{const r=await fetch(`${API}/api/${ep}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token})});const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);if(btn){btn.disabled=false;btn.textContent="Повторить"}return}
state[key]=d[key];
if(stage==='spec')renderSpecPane();else renderAnContent();
}catch(e){alert("Ошибка: "+e.message);if(btn)btn.disabled=false}
}
function renderCanvas(c){const B=(k,cls,l)=>{const b=c[k];return `<div class="cv ${cls}"><div class="cv-h">${l}<span>${b.completeness}%</span></div><ul>${b.items.map(i=>`<li>${esc(i)}</li>`).join("")}</ul>${b.note?`<div class="cv-note">${esc(b.note)}</div>`:''}</div>`};
return `<div class="canvas-grid">${B("key_partners","kp","Партнёры")}${B("key_activities","ka","Активности")}${B("value_propositions","vp","Ценность")}${B("customer_relationships","cr","Отношения")}${B("customer_segments","cs","Сегменты")}${B("key_resources","kr","Ресурсы")}${B("channels","ch","Каналы")}${B("cost_structure","co","Издержки")}${B("revenue_streams","rev","Доходы")}<div class="cv-ins"><b>Вывод:</b> ${esc(c.insight)}</div></div>`}
function renderIdef(m){const box=(fn,ct,ins,outs,me,id,pct)=>{const C=(ct&&ct.length)?ct.map(c=>`<span class="ar">${esc(c.name)}</span>`).join(""):`<span class="ar nomiss">нет управления</span>`;const I=(ins||[]).map(a=>`<span class="ar">${esc(a.name)}</span>`).join("")||'<span class="ar">—</span>';const O=(outs||[]).map(a=>`<span class="ar ${a.target==='НИКУДА'?'dead':''}">${esc(a.name)}${a.target==='НИКУДА'?' ⊘':''}</span>`).join("")||'<span class="ar">—</span>';const M=(me||[]).map(x=>`<span class="ar">${esc(x.name)}</span>`).join("")||'<span class="ar">—</span>';return `<div class="idef"><div class="idef-c">${C}</div><div class="idef-mid"><div class="idef-i">${I}</div><div class="idef-fn">${id?`<b>${id}</b>`:''}${esc(fn)}${pct!=null?`<i style="color:${pct>=70?'#047857':pct>=45?'#F59E0B':'#EF4444'}">${pct}%</i>`:''}</div><div class="idef-o">${O}</div></div><div class="idef-m">${M}</div></div>`};
let h=`<div style="background:var(--sb);color:#fff;border-radius:11px;padding:12px 16px;margin-bottom:14px;font-size:13px"><b style="color:var(--mid)">Паттерн:</b> ${esc(m.business_pattern)}</div>`;
if(m.context){h+=`<div class="idef-lbl">A-0 Контекст</div>`+box(m.context.function,m.context.controls,m.context.inputs,m.context.outputs,m.context.mechanisms,"A0")}
h+=`<div class="idef-lbl">Декомпозиция · ${m.activities.length} функций</div>`;m.activities.forEach(a=>h+=box(a.function,a.controls,a.inputs,a.outputs,a.mechanisms,a.node_id,a.completeness));
if(m.arrow_issues&&m.arrow_issues.length){h+=`<div class="idef-lbl">Разрывы · ${m.arrow_issues.length}</div>`;m.arrow_issues.forEach(g=>{const col=g.severity==='critical'?'#DC2626':g.severity==='high'?'#92400E':'#1E40AF';h+=`<div class="blk" style="border-left:3px solid ${col};padding:11px 14px"><div style="font-size:10px;font-weight:700;color:#9ca3af">${esc(g.node_id)} · ${g.type}</div><div style="font-size:13px;font-weight:700">${esc(g.title)}</div><div style="font-size:12px;color:#6b7280;margin-top:3px">${esc(g.description)}</div></div>`})}
return h}
function renderSpec(s){let h=`<div class="spec-h"><span class="pl">A</span>Обзор</div><div class="blk">${esc(s.overview)}</div>`;
h+=`<div class="spec-h"><span class="pl">A</span>Роли (${s.roles.length})</div>`;s.roles.forEach(r=>h+=`<div class="blk" style="padding:12px 15px"><b>${esc(r.name)}</b> — ${esc(r.does)}<div style="font-size:11px;color:#9ca3af;margin-top:3px">Доступ: ${esc(r.access)}</div></div>`);
h+=`<div class="spec-h"><span class="pl">B</span>Модули (${s.modules.length})</div>`;s.modules.forEach(m=>h+=`<div class="mod"><div class="mod-top"><span class="mod-node">${esc(m.source_node)}</span><span class="mod-name">${esc(m.name)}</span></div><div style="font-size:12px;color:var(--muted)">${esc(m.purpose)}</div><div style="margin:6px 0">${m.screens.map(s=>`<span class="scr">${esc(s)}</span>`).join("")}</div><div class="mod-data">📥 ${esc(m.inputs_data)} · 📤 ${esc(m.outputs_data)}</div>${m.rules.length?`<div style="margin-top:6px">${m.rules.map(r=>`<div class="blk-pain">${esc(r)}</div>`).join("")}</div>`:''}</div>`);
h+=`<div class="spec-h"><span class="pl">C</span>Модель данных (${s.entities.length} таблиц)</div>`;s.entities.forEach(e=>h+=`<div class="ent"><div class="ent-name">◆ ${esc(e.name)}</div><div class="ent-fields">${e.fields.map(f=>`<div class="fld"><b>${esc(f.field)}</b> <em>${esc(f.type)}</em></div>`).join("")}</div>${e.relations.length?`<div style="font-size:11px;color:#6b7280;margin-bottom:6px">🔗 ${e.relations.map(esc).join(" · ")}</div>`:''}<div class="ent-ex">${esc(e.example)}</div></div>`);
if(s.open_questions&&s.open_questions.length){h+=`<div class="blk" style="border-color:#FDE68A;background:#FFFBEB"><div style="font-weight:700;margin-bottom:8px">Уточнить перед разработкой</div>${s.open_questions.map(q=>`<div class="blk-pain">${esc(q)}</div>`).join("")}</div>`}
return h}
inp.addEventListener("input",()=>{inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"});
init();
</script>
</body>
</html>