wasrusgen1-crm/docs/cabinet.html

896 lines
89 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>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<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;color:#fff}
.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;overflow-anchor:none}
/* Chat */
.chat{padding:24px 26px;display:flex;flex-direction:column;gap:14px}
/* Спросить Елену — док на этапах 3-5 */
.askdock{display:none;border-top:1px solid var(--border);background:var(--white);flex:0 0 auto}
.askdock.show{display:block}
.askdock-head{display:flex;align-items:center;gap:8px;padding:11px 18px;cursor:pointer;font-size:13px;font-weight:700;color:var(--primary);user-select:none}
.askdock-head .ad-sub{font-weight:500;color:#9ca3af;font-size:12px}
.askdock-head .ad-chev{margin-left:auto;transition:transform .2s}
.askdock.open .ad-chev{transform:rotate(180deg)}
.askdock-body{display:none;border-top:1px solid var(--border)}
.askdock.open .askdock-body{display:block}
.askdock-thread{max-height:240px;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:10px}
.askdock-thread:empty{display:none}
.askdock-inbar{display:flex;gap:8px;padding:10px 14px;border-top:1px solid var(--border)}
.askdock-inbar textarea{flex:1;border:1px solid var(--border);border-radius:10px;padding:9px 12px;font:inherit;font-size:13px;resize:none;max-height:90px;outline:none}
.askdock-inbar textarea:focus{border-color:var(--primary)}
.am{display:flex;gap:8px;font-size:13px;line-height:1.45}
.am .am-av{width:24px;height:24px;border-radius:50%;flex:0 0 24px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff}
.am.u{flex-direction:row-reverse}
.am.u .am-av{background:#64748b}
.am.e .am-av{background:var(--primary)}
.am .am-bb{background:#f1f5f9;border-radius:10px;padding:7px 11px;max-width:86%}
.am.u .am-bb{background:#e2e8f0}
.am-dev{font-size:11px;color:#92400E;background:#FEF3C7;border-radius:6px;padding:4px 8px;margin-top:5px;font-weight:600}
.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}
.btn-s{background:var(--white);color:var(--primary);border:1.5px solid var(--primary);font-size:13px;padding:9px 16px}
.btn-s:hover{background:var(--light)}
.exp-bar{margin-top:22px;border:1.5px solid var(--border);border-radius:16px;padding:22px;background:var(--white)}
.exp-bar.locked{background:linear-gradient(135deg,#FFFBEB,#FEF3C7);border-color:#FDE68A}
.exp-h{font-family:'Montserrat';font-weight:800;font-size:16px;color:var(--text);margin-bottom:6px}
.exp-d{font-size:13px;color:var(--muted);line-height:1.5;margin-bottom:14px}
.exp-btns{display:flex;flex-wrap:wrap;gap:10px}
@media print{.sb,.hdr,.pb,.hero,.nav,.exp-bar,.modal,.mic,.inp-bar{display:none!important}.main,.sv,.scroll,.pad{display:block!important;overflow:visible!important;height:auto!important;margin:0!important;padding:0!important}body{background:#fff}}
/* ── Мобильный (Telegram на телефоне) ── */
.hdr-burger{display:none;background:none;border:none;color:#fff;font-size:22px;cursor:pointer;padding:0 4px;line-height:1;flex-shrink:0}
.sb-backdrop{display:none;position:fixed;inset:54px 0 0 0;background:rgba(0,0,0,.55);z-index:40}
.sb-backdrop.show{display:block}
@media(max-width:680px){
.hdr{padding:0 12px;gap:8px}
.hdr-burger{display:block}
.hdr-t{font-size:12px}
.hdr-client{display:none}
.elena-nm{display:none}
.sb{position:fixed;left:-260px;top:54px;bottom:0;z-index:50;width:240px;transition:left .25s ease;box-shadow:3px 0 18px rgba(0,0,0,.45)}
.sb.open{left:0}
.main{width:100%}
.hero{padding:13px 15px;gap:11px}
.hero-ic{width:38px;height:38px;font-size:17px}
.hero-h{font-size:16px}
.hero-d{font-size:12px}
.chat{padding:16px 13px;gap:11px}
.msg{max-width:90%}
.bb{font-size:13.5px}
.inbar{padding:10px 13px;gap:8px}
.icon-btn{width:40px;height:40px}
.run-card{padding:20px 16px;margin:8px 14px 16px}
.exp-bar{padding:16px 14px}
}
</style>
</head>
<body>
<header class="hdr">
<button class="hdr-burger" onclick="toggleSb()" aria-label="Меню"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
<div class="hdr-ic"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></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 style="display:inline-flex;align-items:center;gap:7px"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>К оплате: <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>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><a href="#" onclick="viewDoc('offer');return false" style="color:var(--primary);font-weight:600">Договор-оферта на консультационные услуги</a><br>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><a href="#" onclick="viewDoc('pep');return false" style="color:var(--primary);font-weight:600">Соглашение об использовании ПЭП</a><br>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><a href="#" onclick="viewDoc('pdn');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;gap:7px" onclick="signConfirm()"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>Подписать договор</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" style="color:#047857"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg></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" style="color:#047857"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg></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" style="color:#047857"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12h.01M18 12h.01"/></svg></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">
<div class="sb-backdrop" id="sbBackdrop" onclick="toggleSb()"></div>
<aside class="sb" id="sbNav">
<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)"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></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="si3" onclick="go(3)"><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="si4" onclick="go(4)"><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="si5" onclick="go(5)"><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 style="height:1px;background:rgba(255,255,255,.06);margin:8px 18px"></div>
<div class="si" id="si6" onclick="go(6)"><div class="si-num" style="background:rgba(4,120,87,.25);color:#10B981"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div class="si-body"><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"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></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" style="display:flex;align-items:center;justify-content:space-between">Описание деятельности <button type="button" id="pfMic" class="mic" onclick="toggleMic('pfDesc','pfMic')" title="Надиктовать" style="width:30px;height:30px;border-radius:9px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer"><svg width="15" height="15" 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></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"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div><div class="hero-tag">Этап 1 из 4 · В процессе</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>
<!-- Этап 3 — Документы -->
<div class="sv" id="sv3">
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></div><div><div class="hero-tag">Этап 2 из 4</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" style="color:#94A3B8"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></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"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></div><div><div class="hero-tag">Этап 3 из 4</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"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg></div><div><div class="hero-tag">Этап 4 из 4</div><div class="hero-h">План — ТЗ на программу</div><div class="hero-d">Проект системы для вашего бизнеса</div></div></div>
<div class="scroll"><div class="pad" id="specPad"></div></div>
</div>
<!-- Канал «Консультант» (живой) -->
<div class="sv" id="sv6">
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div style="flex:1"><div class="hero-tag">Личный консультант</div><div class="hero-h">Связаться с Русланом</div><div class="hero-d">Вопросы по проекту, срокам, оплате — напишите напрямую</div></div><button onclick="openIdeas()" style="align-self:center;background:#ECFDF5;color:#047857;border:1.5px solid rgba(4,120,87,.25);border-radius:10px;padding:9px 14px;font-size:13px;font-weight:700;font-family:Inter;cursor:pointer;white-space:nowrap;display:inline-flex;align-items:center;gap:6px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg> Мои идеи</button></div>
<div class="scroll"><div class="chat" id="opChat"></div></div>
<div class="inbar">
<textarea class="inp" id="opInp" rows="1" placeholder="Напишите консультанту…" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();opSend()}"></textarea>
<button class="icon-btn mic" id="opMic" title="Голосом" onclick="toggleMic('opInp','opMic')"><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="opSendBtn" onclick="opSend()"><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>
<!-- Спросить Елену — док на этапах 3-5 -->
<div class="askdock" id="askDock">
<div class="askdock-head" onclick="toggleAsk()"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" style="margin-right:6px;vertical-align:-3px"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>Спросить Елену <span class="ad-sub" id="adSub">об этом этапе</span><svg class="ad-chev" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></div>
<div class="askdock-body">
<div class="askdock-thread" id="askThread"></div>
<div class="askdock-inbar">
<textarea id="askInp" rows="1" placeholder="Спросите Елену об этом этапе…" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();askElena()}"></textarea>
<input type="file" id="askFile" multiple style="display:none" onchange="askAttach(this.files)">
<button class="icon-btn" id="askAttachBtn" title="Приложить документ" onclick="document.getElementById('askFile').click()" style="background:var(--light);border:1.5px solid var(--border);color:var(--primary)"><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="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></button>
<button class="icon-btn mic" id="askMic" title="Голосом" onclick="toggleMic('askInp','askMic')"><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="askSend" onclick="askElena()"><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>
</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:25,3:50,4:75,5:100}; // 4 шага для клиента (Этап 1 объединён)
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>")}
/* ── Иконки по брендбуку: Lucide, stroke 1.75, fill none, round ── */
const ICONS={
user:'<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
message:'<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
folder:'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>',
file:'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>',
chart:'<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
activity:'<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>',
process:'<circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 19.07a10 10 0 0 1 0-14.14"/>',
team:'<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
clipboard:'<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>',
idea:'<path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/>',
paperclip:'<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/>',
card:'<rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/>',
phone:'<rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>',
cash:'<rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12h.01M18 12h.01"/>',
printer:'<polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>',
download:'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>',
lock:'<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
calendar:'<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>',
check:'<polyline points="20 6 9 17 4 12"/>',
alert:'<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
mic:'<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"/>',
receipt:'<path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1-2-1z"/><path d="M16 8H8M16 12H8M13 16H8"/>',
search:'<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>',
target:'<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
send:'<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>',
close:'<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
checkCircle:'<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
sparkle:'<path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"/>',
gift:'<polyline points="20 12 20 22 4 22 4 12"/><rect x="2" y="7" width="20" height="5"/><line x1="12" y1="22" x2="12" y2="7"/><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/>',
dot:'<circle cx="12" cy="12" r="5"/>'
};
function ic(n,s,sw){s=s||20;return '<svg style="display:inline-block;vertical-align:middle" width="'+s+'" height="'+s+'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="'+(sw||1.75)+'" stroke-linecap="round" stroke-linejoin="round">'+(ICONS[n]||'')+'</svg>';}
function toggleSb(){
const sb=document.getElementById('sbNav'), bd=document.getElementById('sbBackdrop');
const open=sb.classList.toggle('open'); bd.classList.toggle('show',open);
}
function closeSb(){
const sb=document.getElementById('sbNav'), bd=document.getElementById('sbBackdrop');
if(sb)sb.classList.remove('open'); if(bd)bd.classList.remove('show');
}
function go(n){
if(document.getElementById('si'+n).classList.contains('locked'))return;
if(window.innerWidth<=680)closeSb();
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===1){renderChat1();requestAnimationFrame(scrollChatBottom);} // ре-рендер из общей беседы
if(n===6){renderOpChat();startOpPoll();}else{stopOpPoll();}
if(n===3)renderDocs();
if(n===4)renderAnalysis();
if(n===5)renderSpecPane();
// Telegram: нативная «Назад» + лёгкий хаптик
const _tg=window.__tg;
if(_tg){try{if(_tg.BackButton){if(n&&n!==1)_tg.BackButton.show();else _tg.BackButton.hide();}if(_tg.HapticFeedback)_tg.HapticFeedback.selectionChanged();}catch(e){}}
// Док «Спросить Елену» — только на этапах 3-5
const dock=document.getElementById("askDock");
if(dock){
if(n>=3&&n<=5){dock.classList.add("show");dock.classList.add("open");document.getElementById("adSub").textContent=STAGE_LBL[n]||"об этапе";
if(!dock.dataset.rendered){renderAskThread();dock.dataset.rendered="1";}}
else dock.classList.remove("show");
}
}
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,micTarget=null,micBtnEl=null;
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;if(micTarget){micTarget.value=(base+intr).trim();if(micTarget.tagName==='TEXTAREA'){micTarget.style.height="auto";micTarget.style.height=Math.min(micTarget.scrollHeight,120)+"px";}}};
recog.onstart=()=>{base=(micTarget&&micTarget.value)?micTarget.value+" ":""};
recog.onend=()=>{if(recording){try{recog.start()}catch(e){}}};
recog.onerror=e=>{if(e.error==="not-allowed"){alert("Разрешите доступ к микрофону");stopMic()}};
}
function toggleMic(targetId,btnId){
if(!recog){alert("Голосовой ввод работает в Chrome / Android. На iPhone — печатайте.");return}
const t=document.getElementById(targetId||'inp'), b=document.getElementById(btnId||'micBtn');
if(recording&&micTarget===t){stopMic();return;}
if(recording)stopMic();
micTarget=t;micBtnEl=b;startMic();
}
function startMic(){recording=true;if(micBtnEl)micBtnEl.classList.add("rec");if(micBtnEl&&micBtnEl.id==='micBtn'){const h=document.getElementById("micHint");if(h)h.classList.add("show");}try{recog.start()}catch(e){}}
function stopMic(){recording=false;if(micBtnEl)micBtnEl.classList.remove("rec");const h=document.getElementById("micHint");if(h)h.classList.remove("show");try{recog.stop()}catch(e){}if(micTarget)micTarget.focus();}
/* ── Chat ── */
// Скроллит реальный контейнер (.scroll), а не .chat (у него нет своего скролла). Мгновенно, без анимации.
function scrollChatBottom(){const sc=chat.parentElement;if(sc)sc.scrollTop=sc.scrollHeight;}
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);scrollChatBottom()}
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);scrollChatBottom()}
function hideTyping(){const t=document.getElementById("typing");if(t)t.remove()}
/* ── Канал «Консультант» (живой) ── */
function opMsg(role,text){const c=document.getElementById("opChat");if(!c)return;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>`;c.appendChild(m);const sc=c.parentElement;if(sc)sc.scrollTop=sc.scrollHeight;}
function renderOpChat(){const c=document.getElementById("opChat");if(!c)return;c.innerHTML="";const m=state&&state.operator_chat||[];if(!m.length){c.innerHTML='<div style="text-align:center;color:#cbd5e1;font-size:13px;padding:24px 16px">Напишите консультанту — Руслан ответит здесь и пришлёт уведомление в Telegram.</div>';return;}m.forEach(x=>opMsg(x.role==="user"?"user":"elena",x.content));}
async function opSend(){const inp=document.getElementById("opInp");const t=inp.value.trim();if(!t)return;inp.value="";inp.style.height="auto";opMsg("user",t);state.operator_chat=state.operator_chat||[];state.operator_chat.push({role:"user",content:t});
try{const r=await fetch(`${API}/api/operator-chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,message:t})});const d=await r.json();if(d.messages)state.operator_chat=d.messages;}catch(e){opMsg("elena","Не доставлено: "+e.message);}
}
let opPollTimer=null;
async function opPollOnce(){try{const r=await fetch(`${API}/api/operator-chat?token=${encodeURIComponent(token)}`);const d=await r.json();if(d.messages&&d.messages.length!==(state.operator_chat||[]).length){state.operator_chat=d.messages;renderOpChat();}}catch(e){}}
function startOpPoll(){stopOpPoll();opPollOnce();opPollTimer=setInterval(opPollOnce,15000);}
function stopOpPoll(){if(opPollTimer){clearInterval(opPollTimer);opPollTimer=null;}}
/* ── Предложения клиента ── */
const SUG_ST={new:[ic('dot',12)+' Новое','#EFF6FF','#2563EB'],discussion:[ic('message',12)+' На обсуждении','#FEF3C7','#92400E'],accepted:[ic('checkCircle',12)+' Принято','#D1FAE5','#047857'],rejected:[ic('close',12)+' Отклонено','#FEE2E2','#B91C1C']};
function openIdeas(){
const sgs=state.suggestions||[];
const list=sgs.length?sgs.map(s=>{const st=SUG_ST[s.status]||SUG_ST.new;return `<div style="border:1px solid var(--border);border-radius:10px;padding:10px 12px;margin-bottom:8px"><div style="font-size:13px">${esc(s.text)}</div><div style="margin-top:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="font-size:11px;font-weight:700;color:${st[2]};background:${st[1]};padding:2px 8px;border-radius:6px">${st[0]}</span>${s.decision?`<span style="font-size:11px;color:#6B7280">— ${esc(s.decision)}</span>`:''}</div></div>`}).join(''):'<div style="font-size:13px;color:#9ca3af;text-align:center;padding:10px">Пока нет. Предложите первую идею — мы рассмотрим её.</div>';
const m=document.createElement('div');m.id='ideaModal';m.style.cssText='position:fixed;inset:0;background:rgba(15,15,26,.5);z-index:260;display:flex;align-items:center;justify-content:center;padding:18px';m.onclick=()=>m.remove();
m.innerHTML=`<div onclick="event.stopPropagation()" style="background:#fff;border-radius:16px;padding:22px;width:100%;max-width:420px;max-height:84vh;display:flex;flex-direction:column">
<div style="font-size:18px;font-weight:800;font-family:Montserrat,Inter;margin-bottom:4px;display:flex;align-items:center;gap:8px">${ic('idea',20)} Мои предложения</div>
<div style="font-size:12px;color:#6B7280;margin-bottom:12px">Ваши идеи по проекту. Консультант рассмотрит каждую и ответит решением.</div>
<div style="overflow-y:auto;flex:1;margin-bottom:12px">${list}</div>
<textarea id="ideaInp" rows="2" placeholder="Опишите идею или улучшение…" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:10px 12px;font-size:13px;font-family:Inter;resize:vertical;outline:none;box-sizing:border-box"></textarea>
<div style="display:flex;gap:8px;margin-top:10px"><button onclick="document.getElementById('ideaModal').remove()" style="flex:1;padding:11px;background:#F1F5F9;color:#475569;border:none;border-radius:10px;font-weight:700;font-family:Inter;cursor:pointer">Закрыть</button><button id="ideaOk" onclick="submitIdea()" style="flex:1.4;padding:11px;background:#047857;color:#fff;border:none;border-radius:10px;font-weight:700;font-family:Inter;cursor:pointer">Предложить →</button></div>
</div>`;
document.body.appendChild(m);setTimeout(()=>{const e=document.getElementById('ideaInp');if(e)e.focus();},40);
}
async function submitIdea(){
const t=(document.getElementById('ideaInp').value||'').trim();if(!t)return;
const b=document.getElementById('ideaOk');b.disabled=true;b.textContent='Отправляю…';
try{const r=await fetch(`${API}/api/suggestion`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,text:t})});const d=await r.json();
if(d.suggestions)state.suggestions=d.suggestions;
const mm=document.getElementById('ideaModal');if(mm)mm.remove();openIdeas();
}catch(e){b.disabled=false;b.textContent='Предложить →';}
}
/* ── Спросить Елену (этапы 3-5) ── */
const STAGE_LBL={3:"о документах",4:"о стратегии и модели",5:"о ТЗ и плане"};
function toggleAsk(){document.getElementById("askDock").classList.toggle("open")}
function addAsk(role,text,dev){
const t=document.getElementById("askThread");if(!t)return;
const m=document.createElement("div");m.className="am "+(role==="user"?"u":"e");
m.innerHTML=`<div class="am-av">${role==="user"?"Я":"Е"}</div><div class="am-bb">${fmt(text)}${dev?'<div class="am-dev">'+ic('alert',12)+' Ваше пожелание зафиксировано — учтём при внедрении</div>':''}</div>`;
t.appendChild(m);t.scrollTop=t.scrollHeight;
}
function renderAskThread(){
const t=document.getElementById("askThread");if(!t)return;
t.innerHTML="";(state&&state.qa||[]).forEach(m=>addAsk(m.role==="user"?"user":"elena",m.content));
}
async function askAttach(files){
document.getElementById("askDock").classList.add("open");
for(const f of files){
addAsk("user","Файл: "+f.name);
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){addAsk("elena","Не удалось загрузить «"+f.name+"»: "+d.error);continue;}
state.documents=state.documents||[];state.documents.push({filename:d.filename,size:d.size});
addAsk("elena","Документ «"+f.name+"» приложен — учту его в ответах по проекту. Спрашивайте.");
}catch(e){addAsk("elena","Ошибка загрузки: "+e.message);}
}
document.getElementById("askFile").value="";
}
async function askElena(){
const inp=document.getElementById("askInp");const text=inp.value.trim();if(!text)return;
inp.value="";inp.style.height="auto";
document.getElementById("askDock").classList.add("open");
addAsk("user",text);
const btn=document.getElementById("askSend");btn.disabled=true;
const tp=document.createElement("div");tp.className="am e";tp.id="askTyping";tp.innerHTML='<div class="am-av">Е</div><div class="am-bb">…</div>';
document.getElementById("askThread").appendChild(tp);
try{
const r=await fetch(`${API}/api/ask`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,message:text,stage:String(cur)})});
const d=await r.json();
const x=document.getElementById("askTyping");if(x)x.remove();
addAsk("elena",d.reply||("Ошибка: "+(d.error||"?")),d.deviation_recorded);
state.qa=state.qa||[];state.qa.push({role:"user",content:text},{role:"assistant",content:d.reply||""});
}catch(e){const x=document.getElementById("askTyping");if(x)x.remove();addAsk("elena","Ошибка связи: "+e.message)}
btn.disabled=false;
}
async function init(){
// ── Telegram Mini App: полноценная интеграция ──
const tg=window.Telegram&&window.Telegram.WebApp;
if(tg){try{
tg.ready();tg.expand();
const sp=tg.initDataUnsafe&&tg.initDataUnsafe.start_param;if(sp&&!token){token=sp;localStorage.setItem("cab_token",token);}
// Тема под бренд: тёмная шапка, светлый фон
if(tg.setHeaderColor)tg.setHeaderColor('#0F0F1A');
if(tg.setBackgroundColor)tg.setBackgroundColor('#F5F6F8');
// Защита от случайного смахивания вниз (не терять диалог)
if(tg.enableClosingConfirmation)tg.enableClosingConfirmation();
// Нативная кнопка «Назад» Telegram → к разговору с Еленой
if(tg.BackButton){tg.BackButton.onClick(()=>go(1));}
window.__tg=tg;
}catch(e){}}
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 _src=new URLSearchParams(location.search).get('src');
const _body=_src?{source:_src==='landing'?'Лендинг':_src}:{};
const r=await fetch(`${API}/api/project/new`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_body)});
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=>{const u=`${API}/api/doc?token=${encodeURIComponent(token)}&name=${encodeURIComponent(d.filename)}`;return `<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="color:#047857;display:inline-flex">${ic('file',18)}</span><div style="flex:1;min-width:0"><a href="${u}" target="_blank" rel="noopener" style="font-size:13px;font-weight:600;color:var(--primary);text-decoration:none">${esc(d.filename)}</a><div style="font-size:11px;color:#9ca3af">${(d.size/1024).toFixed(0)} КБ · учтён в анализе</div></div><a href="${u}&dl=1" title="Скачать" style="color:#9ca3af;text-decoration:none;display:inline-flex">${ic('download',16)}</a></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=`${ic('alert',13)} ${esc(f.name)}: ${d.error}`;continue}
state.documents=state.documents||[];state.documents.push({filename:d.filename,size:d.size});
}catch(e){tmp.innerHTML=`${ic('alert',13)} ${esc(f.name)}: ${e.message}`;continue}
}
renderDocs();
}
function renderAll(){
if(state.client_name)document.getElementById("hdrClient").textContent="· "+state.client_name;
renderChat1();
unlockStages();
checkPayment();
}
function renderChat1(){const c=document.getElementById("chat");if(!c)return;c.innerHTML="";(state.messages||[]).forEach(m=>addMsg(m.role==="user"?"user":"elena",m.content));}
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={offer:"Договор-оферта",pep:"Соглашение об использовании ПЭП",pdn:"Политика обработки персональных данных"}[doc]||"Документ";
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;
[3,4,5].forEach(n=>{const si=document.getElementById('si'+n);if(si&&(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')">${ic('chart',16)} Стратегия</div><div class="an-tab ${anTab==='idef0'?'active':''}" onclick="setAnTab('idef0')">${ic('process',16)} Функции</div><div class="an-tab ${anTab==='org'?'active':''}" onclick="setAnTab('org')">${ic('team',16)} Организация</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",ic('chart',30),"Стратегическая модель","Елена построит Business Model Canvas — как устроен ваш бизнес и как он зарабатывает.","Построить стратегию →");return}
c.innerHTML=renderCanvas(state.canvas);
}else if(anTab==='idef0'){
if(!state.model){c.innerHTML=runCard("model",ic('process',30),"Функциональная модель","Елена разложит бизнес на функции (IDEF0): входы, выходы, нормы, ресурсы и разрывы.","Построить модель →");return}
c.innerHTML=renderIdef(state.model);
}else{
c.innerHTML=renderOrg();
}
}
function renderOrg(){
if(!state.model)return runCard(null,ic('alert',30),"Сначала функции","Оргструктура строится из функциональной модели. Постройте модель на вкладке «Функции».","→ К функциям",()=>setAnTab('idef0'));
let h='';
if(!state.orgchart)h+=runCard("orgchart",ic('team',30),"Целевая оргструктура","Елена построит оргструктуру из модели: кто за что отвечает, подчинённость, штат, узкие места.","Построить оргструктуру →");
else h+=renderOrgChart(state.orgchart);
if(state.orgchart){
if(!state.jobs)h+=`<div style="height:14px"></div>`+runCard("jobs",ic('clipboard',30),"Должностные инструкции","По ролям: зоны ответственности, KPI, полномочия. С учётом ваших пожеланий (отклонений).","Собрать инструкции →");
else h+=`<div style="height:14px"></div>`+renderJobs(state.jobs);
}
return h;
}
function renderOrgChart(o){
const units=o.units||[];
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(o.insight||'')}</div>`;
h+=units.map(u=>`<div style="background:var(--white);border:1px solid var(--border);border-radius:11px;padding:12px 14px;margin-bottom:8px">
<div style="display:flex;align-items:center;gap:9px;flex-wrap:wrap">
<span style="font-size:14px;font-weight:800">${esc(u.role||'')}</span>
<span style="font-size:11px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 8px;border-radius:5px">${(+u.headcount||1)} чел.</span>
${u.reports_to&&u.reports_to!=='—'?`<span style="font-size:11px;color:#9ca3af">↑ ${esc(u.reports_to)}</span>`:'<span style="font-size:10px;font-weight:700;color:#6366F1;background:#EEF2FF;padding:1px 8px;border-radius:5px">руководство</span>'}
</div>
${(u.owns_functions&&u.owns_functions.length)?`<div style="font-size:11px;color:#6b7280;margin-top:6px">Отвечает: ${u.owns_functions.map(f=>`<span style="background:#F3F4F6;padding:1px 6px;border-radius:4px;margin-right:3px;display:inline-block">${esc(f)}</span>`).join('')}</div>`:''}
${u.note?`<div style="font-size:11px;color:#92400E;background:#FEF3C7;border-radius:6px;padding:5px 9px;margin-top:6px">${ic('alert',12)} ${esc(u.note)}</div>`:''}
</div>`).join('');
return h;
}
function renderJobs(j){
const roles=j.roles||[];
return `<div style="font-size:13px;font-weight:800;margin-bottom:10px;display:flex;align-items:center;gap:7px">${ic('clipboard',17)} Должностные инструкции</div>`+roles.map(r=>`<div style="background:var(--white);border:1px solid var(--border);border-radius:11px;padding:14px 16px;margin-bottom:10px">
<div style="font-size:14px;font-weight:800">${esc(r.role||'')}</div>
<div style="font-size:12px;color:#6b7280;margin:3px 0 9px">${esc(r.purpose||'')}${r.reports_to?` · ↑ ${esc(r.reports_to)}`:''}</div>
${(r.responsibilities&&r.responsibilities.length)?`<div style="font-size:11px;font-weight:700;color:#374151;margin-bottom:3px">Зоны ответственности</div><ul style="margin:0 0 9px;padding-left:18px;font-size:12px;color:#4B5563">${r.responsibilities.map(x=>`<li>${esc(x)}</li>`).join('')}</ul>`:''}
${(r.kpis&&r.kpis.length)?`<div style="font-size:11px;font-weight:700;color:#047857;margin-bottom:3px">KPI</div><div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:9px">${r.kpis.map(k=>`<span style="font-size:11px;background:#ECFDF5;color:#047857;padding:2px 8px;border-radius:5px">${esc(k)}</span>`).join('')}</div>`:''}
${(r.authority&&r.authority.length)?`<div style="font-size:11px;color:#6b7280">Полномочия: ${r.authority.map(a=>esc(a)).join(' · ')}</div>`:''}
${r.deviation_note?`<div style="font-size:11px;color:#92400E;background:#FEF3C7;border-radius:6px;padding:5px 9px;margin-top:8px">${ic('alert',12)} Учтено пожелание клиента: ${esc(r.deviation_note)}</div>`:''}
</div>`).join('');
}
function renderSpecPane(){
const pad=document.getElementById("specPad");
if(!state.spec){
if(!state.model){pad.innerHTML=runCard(null,ic('alert',30),"Сначала Анализ","ТЗ собирается из функциональной модели. Перейдите на Этап 4 и постройте модель.","← К анализу",()=>go(4));return}
pad.innerHTML=runCard("spec",ic('clipboard',30),"Техническое задание","Из модели бизнеса Елена спроектирует программу: роли, модули, экраны, данные.","Собрать ТЗ →");return}
pad.innerHTML=renderSpec(state.spec)+renderExportBar();
}
const EST_STAGES=[
{key:"interview", name:"Интервью", icon:ic('message',15), desc:"Диагностика «как есть» — карта проблем"},
{key:"methods", name:"Методологии", icon:ic('target',15), desc:"Подбор фреймворков под вашу задачу"},
{key:"canvas", name:"Стратегия", icon:ic('chart',15), desc:"Целевая модель процессов (TO-BE)"},
{key:"idef0", name:"Функции IDEF0", icon:ic('process',15), desc:"Декомпозиция функций + регламенты"},
{key:"org", name:"Организация", icon:ic('team',15), desc:"Оргструктура + должностные инструкции"},
{key:"spec", name:"Выдача ТЗ", icon:ic('clipboard',15), desc:"Готовое ТЗ к внедрению"},
];
function estStageDone(k){
if(k==="interview")return (state.messages||[]).length>0;
if(k==="methods") return !!state.selection;
if(k==="canvas") return !!state.canvas;
if(k==="idef0") return !!state.model;
if(k==="org") return !!(state.orgchart && state.jobs);
if(k==="spec") return !!state.spec;
return false;
}
function renderEstimateCard(){
const crm=state.crm||{};
const sp=crm.stage_prices;
if(!sp)return ''; // смета ещё не сформирована — не показываем
const pays=crm.stage_payments||{};
const due=crm.stage_due||{};
const fmtD=d=>{if(!d)return'';const a=d.split('-');return a[2]+'.'+a[1]+'.'+a[0].slice(2);};
const total=Object.values(sp).reduce((a,b)=>a+(+b||0),0);
const paid=Object.values(pays).reduce((s,p)=>s+(p.amount||0),0);
return `<div class="exp-bar" style="margin-bottom:14px">
<div class="exp-h" style="display:flex;align-items:center;gap:7px">${ic('receipt',18)} Смета проекта</div>
<div class="exp-d" style="margin-bottom:10px">Прозрачно: за что и сколько. Интервью — бесплатно, дальше помодульно. Готовое ТЗ — после полной оплаты.</div>
${EST_STAGES.map(s=>{
const price=+sp[s.key]||0, isFree=price<=0, isPaid=!!pays[s.key], done=estStageDone(s.key);
const dd=due[s.key]||'';
let tag='';
if(isFree)tag='<span style="font-size:10px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 7px;border-radius:5px">бесплатно</span>';
else if(isPaid)tag='<span style="font-size:10px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 7px;border-radius:5px">'+ic('check',11)+' оплачено</span>';
else if(done)tag='<span style="font-size:10px;font-weight:700;color:#92400E;background:#FEF3C7;padding:1px 7px;border-radius:5px">к оплате</span>';
else tag='<span style="font-size:10px;font-weight:700;color:#9CA3AF;background:#F3F4F6;padding:1px 7px;border-radius:5px">в работе</span>';
const dueChip=(dd&&!isPaid&&!isFree)?`<span style="font-size:10px;font-weight:600;color:#2563EB;background:#EFF6FF;padding:1px 7px;border-radius:5px">${ic('calendar',11)} до ${fmtD(dd)}</span>`:'';
return `<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid rgba(0,0,0,.06)">
<span style="font-size:15px">${s.icon}</span>
<div style="flex:1;min-width:0"><div style="font-size:13px;font-weight:700;display:flex;align-items:center;gap:7px;flex-wrap:wrap">${s.name} ${tag} ${dueChip}</div><div style="font-size:11px;color:#6b7280;margin-top:1px">${s.desc}</div></div>
<span style="font-size:13px;font-weight:700;color:${isFree?'#9CA3AF':'#047857'};white-space:nowrap">${isFree?'0 ₽':money(price)}</span>
</div>`;
}).join('')}
<div style="display:flex;align-items:center;padding:11px 0 2px;border-top:2px solid rgba(0,0,0,.1);margin-top:3px">
<span style="font-size:13px;font-weight:800">Итого</span>
<span style="margin-left:auto;font-size:17px;font-weight:800;color:#047857">${money(total)}</span>
</div>
${paid>0?`<div style="font-size:11px;color:#6b7280;margin-top:4px">Оплачено: <b style="color:#047857">${money(paid)}</b> · остаток ${money(Math.max(0,total-paid))}</div>`:''}
</div>`;
}
function renderExportBar(){
if(state.unlocked){
return renderEstimateCard()+`<div class="exp-bar"><div class="exp-h" style="display:flex;align-items:center;gap:7px">${ic('crm',17)} Готовые документы</div>
<div class="exp-d">Долг закрыт — документы и ТЗ доступны для печати и выгрузки.</div>
<div class="exp-btns">
<button class="btn btn-p" onclick="printDoc()">${ic('printer',16)} Печать / PDF</button>
<button class="btn btn-s" onclick="downloadTZ()">${ic('download',16)} Скачать ТЗ</button>
<button class="btn btn-s" onclick="exportDev()">{ } Выгрузить для разработчика</button>
</div></div>`;
}
const debt=state.debt||0, deal=state.deal_amount||0;
const moneyStr=deal>0?`Сумма: ${deal.toLocaleString('ru')}${debt>0?` · к оплате: <b>${debt.toLocaleString('ru')} ₽</b>`:''}`:'';
return renderEstimateCard()+`<div class="exp-bar locked">
<div class="exp-h" style="display:flex;align-items:center;gap:7px">${ic('lock',17)} Документы готовы — доступны после оплаты</div>
<div class="exp-d">Вы видите полный результат на экране. Печатный документ и выгрузка ТЗ для разработчика открываются после закрытия оплаты.<br>${moneyStr}</div>
<div class="exp-btns"><button class="btn btn-p" onclick="(window.showPayModal||function(){alert('Оплата скоро будет доступна')})()">Оплатить и забрать документы →</button></div>
</div>`;
}
function buildTZmd(s){
let m=`# ТЗ · ${state.client_name||''}\n# @wasrusgen1 | КОНСАЛТИНГ\n\n## A. Обзор\n${s.overview||''}\n\n## A. Роли\n`;
(s.roles||[]).forEach(r=>m+=`- **${r.name}** — ${r.does} (доступ: ${r.access})\n`);
m+=`\n## B. Модули\n`;(s.modules||[]).forEach(x=>{m+=`### ${x.name} [${x.source_node}]\n${x.purpose}\nЭкраны: ${(x.screens||[]).join(', ')}\nДанные: вход — ${x.inputs_data}; выход — ${x.outputs_data}\n`;(x.rules||[]).forEach(r=>m+=`- правило: ${r}\n`);m+=`\n`});
m+=`## C. Модель данных\n`;(s.entities||[]).forEach(e=>{m+=`### ${e.name}\n`;(e.fields||[]).forEach(f=>m+=`- ${f.field}: ${f.type}\n`);if((e.relations||[]).length)m+=`Связи: ${e.relations.join(' · ')}\n`;m+=`Пример: ${e.example}\n\n`});
if(state.orgchart&&(state.orgchart.units||[]).length){m+=`\n## D. Оргструктура\n${state.orgchart.insight||''}\n`;state.orgchart.units.forEach(u=>{m+=`- **${u.role}** (${(+u.headcount||1)} чел.)${u.reports_to&&u.reports_to!=='—'?`${u.reports_to}`:''}${(u.owns_functions||[]).length?` — отвечает: ${u.owns_functions.join(', ')}`:''}${u.note?` (внимание: ${u.note})`:''}\n`})}
if(state.jobs&&(state.jobs.roles||[]).length){m+=`\n## E. Должностные инструкции\n`;state.jobs.roles.forEach(r=>{m+=`### ${r.role}\n${r.purpose||''}${r.reports_to?` (подчинение: ${r.reports_to})`:''}\n`;(r.responsibilities||[]).forEach(x=>m+=`- ${x}\n`);if((r.kpis||[]).length)m+=`KPI: ${r.kpis.join(' · ')}\n`;if((r.authority||[]).length)m+=`Полномочия: ${r.authority.join(' · ')}\n`;if(r.deviation_note)m+=`Внимание — учтено пожелание клиента: ${r.deviation_note}\n`;m+=`\n`})}
if((s.open_questions||[]).length){m+=`## Уточнить перед разработкой\n`;s.open_questions.forEach(q=>m+=`- ${q}\n`)}
return m;
}
function dl(name,text,type){const b=new Blob([text],{type:type||'text/plain;charset=utf-8'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),2000)}
function printDoc(){if(!state.unlocked)return alert('Доступно после оплаты');window.print()}
function downloadTZ(){if(!state.unlocked)return alert('Доступно после оплаты');dl(`ТЗ_${(state.client_name||'проект').replace(/\s+/g,'_')}.md`,buildTZmd(state.spec),'text/markdown;charset=utf-8')}
function exportDev(){if(!state.unlocked)return alert('Доступно после оплаты');dl(`ТЗ_${(state.client_name||'проект').replace(/\s+/g,'_')}.json`,JSON.stringify({client:state.client_name,niche:state.niche,model:state.model,orgchart:state.orgchart,jobs:state.jobs,spec:state.spec},null,2),'application/json')}
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"],orgchart:["build-orgchart","orgchart"],jobs:["build-jobs","jobs"]};
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>