zashita-brandbook/mockup.html

9425 lines
575 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>ЗАЩИТА — прототип (весь путь)</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Montserrat:wght@700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script>
(function(){
var tg = window.Telegram && window.Telegram.WebApp;
if(tg){
tg.ready();
tg.expand();
document.documentElement.classList.add('tma');
// Нативная кнопка «назад» TG
tg.BackButton.onClick(function(){
var screens = document.querySelectorAll('.screen.on');
if(screens.length && screens[0].id !== 'start'){ window.go('start'); }
});
}
})();
</script>
<link rel="stylesheet" href="tokens.css">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--font-ui);background:var(--surf);color:var(--ink);line-height:1.5}
.screen{display:none;min-height:100vh}
.screen.on{display:block}
.brand{font-family:var(--font-logo);font-weight:700;letter-spacing:2px}
.btn{border:none;border-radius:11px;padding:13px 20px;font-size:15px;font-weight:700;cursor:pointer;font-family:inherit}
.btn-p{background:var(--bg);color:#fff} .btn-p:hover{background:var(--bghv)}
.btn-o{background:#fff;color:var(--bg);border:1.5px solid var(--bg)}
.chip{font-size:11px;font-weight:700;padding:3px 9px;border-radius:20px}
.chip.d{background:var(--dngbg);color:var(--dng)} .chip.w{background:var(--warnbg);color:var(--warn)} .chip.n{background:var(--surf);color:var(--mut)} .chip.ok{background:var(--okbg);color:var(--ok)}
/* ── 1. СТАРТ ── */
#start{background:linear-gradient(150deg,#2A020D,var(--bg));color:#fff;display:none;align-items:center}
#start.on{display:flex}
.hero{max-width:1000px;margin:0 auto;padding:50px 32px;display:grid;grid-template-columns:1.2fr .8fr;grid-template-rows:auto 1fr;gap:0 40px;align-items:start}
.hero-logo{grid-column:1;grid-row:1;display:flex;align-items:center;gap:0;margin-bottom:28px}
.hero-logomark{height:34px;filter:brightness(0) invert(1);flex-shrink:0}
.hero-logo-sep{width:1.5px;height:26px;background:rgba(255,255,255,.35);margin:0 16px;flex-shrink:0}
.hero-wordmark{height:17px;filter:brightness(0) invert(1);flex-shrink:0}
.hero-body{grid-column:1;grid-row:2}
.hero .face{grid-column:2;grid-row:1/3;align-self:stretch}
.hero h1{font-size:40px;font-weight:800;letter-spacing:-1px;line-height:1.1;margin-bottom:16px}
.hero p{font-size:17px;color:rgba(255,255,255,.75);margin-bottom:28px;max-width:460px}
.hero .cta{display:flex;gap:12px;flex-wrap:wrap}
.hero .priv{margin-top:20px;font-size:12px;color:rgba(255,255,255,.5)}
.hero .face img{width:100%;height:100%;min-height:380px;object-fit:cover;object-position:center 18%;border-radius:20px;display:block;box-shadow:0 20px 50px rgba(0,0,0,.4)}
.hero .face .cap{text-align:center;font-size:13px;color:rgba(255,255,255,.7);margin-top:10px}
/* ── общий чат (Елена) ── */
.topbar{background:linear-gradient(90deg,var(--shell),var(--shell2));color:#fff;display:flex;align-items:center;gap:12px;padding:12px 22px}
.topbar .w{font-size:14px;color:#fff} .topbar .sep{width:1.5px;height:16px;background:rgba(255,255,255,.3)}
.topbar-wm{height:13px;filter:brightness(0) invert(1);vertical-align:middle;flex-shrink:0}
.topbar .ttl{font-size:13px;color:rgba(255,255,255,.8);margin-left:6px}
.back{margin-left:auto;font-size:12px;color:rgba(255,255,255,.6);cursor:pointer}
.chatwrap{max-width:660px;margin:0 auto;padding:24px 18px 120px}
.msg{display:flex;gap:11px;margin-bottom:16px;align-items:flex-start}
.av{width:40px;height:40px;border-radius:50%;overflow:hidden;flex-shrink:0;border:1.5px solid var(--line)}
.av img{width:100%;height:100%;object-fit:cover;object-position:center 16%}
.bubble{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:13px 16px;font-size:14px;max-width:80%;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.bubble .nm{font-size:11px;font-weight:700;color:var(--bg);margin-bottom:4px}
.bubble b{color:var(--bg)}
.risk-mini{border-top:1px dashed var(--line);padding:8px 0 0;margin-top:8px;font-size:12.5px}
.risk-mini .rn{color:var(--bg);font-weight:600;font-size:11px}
.lock{background:var(--tint);border:1px dashed var(--bg);border-radius:10px;padding:10px 12px;margin-top:10px;font-size:12.5px;color:var(--dark)}
.forks{display:flex;gap:10px;margin:4px 0 4px 51px;flex-wrap:wrap}
.fork{background:var(--card);border:1.5px solid var(--line);border-radius:13px;padding:12px 14px;cursor:pointer;font-size:13px;font-weight:700;flex:1;min-width:180px}
.fork:hover{border-color:var(--bg);transform:translateY(-1px)} .fork .ds{font-weight:400;color:var(--mut);font-size:12px;margin-top:3px}
.actbar{position:fixed;bottom:0;left:0;right:0;background:linear-gradient(180deg,transparent,var(--surf) 30%);padding:18px;display:flex;justify-content:center}
.actbar .inner{max-width:660px;width:100%;display:flex;gap:10px}
/* ── 3. ОПЛАТА ── */
.pay{max-width:560px;margin:0 auto;padding:40px 22px}
.pay h2{font-size:24px;font-weight:800;margin-bottom:6px} .pay .s{color:var(--mut);font-size:14px;margin-bottom:24px}
.plan{background:var(--card);border:1.5px solid var(--line);border-radius:16px;padding:18px;margin-bottom:14px}
.plan.sel{border-color:var(--bg);box-shadow:0 4px 16px rgba(159,18,57,.12)}
.plan .pn{font-weight:800;font-size:16px} .plan .pp{font-weight:800;font-size:20px;color:var(--bg);float:right}
.plan .pd{font-size:13px;color:var(--mut);margin-top:4px}
.field{margin:14px 0}.field label{font-size:12px;font-weight:600;display:block;margin-bottom:5px}
.field input{width:100%;border:1.5px solid var(--line);border-radius:10px;padding:11px 13px;font-size:14px;font-family:inherit}
.pdn{font-size:11px;color:var(--mut);margin:10px 0 16px}
/* ── PAY HYBRID ── */
.pay-bal{background:linear-gradient(135deg,#4f46e5 0%,#7c3aed 100%);color:#fff;border-radius:14px;padding:14px 18px;display:flex;align-items:center;gap:12px;margin-bottom:20px}
.pay-bal-ico{font-size:22px;flex-shrink:0}
.pay-bal-body{flex:1}
.pay-bal-lbl{font-size:11px;opacity:.75;font-weight:600;text-transform:uppercase;letter-spacing:.05em}
.pay-bal-val{font-size:20px;font-weight:800;line-height:1.1}
.pay-bal-sub{font-size:11px;opacity:.75;margin-top:2px}
.pay-bal-use{background:rgba(255,255,255,.2);border:1.5px solid rgba(255,255,255,.35);color:#fff;border-radius:8px;padding:7px 14px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;flex-shrink:0}
.pay-bal-use:hover{background:rgba(255,255,255,.3)}
.pay-tabs{display:flex;gap:8px;margin-bottom:20px}
.pay-tab{flex:1;padding:10px;border:2px solid #e5e7eb;border-radius:12px;text-align:center;cursor:pointer;font-size:13px;font-weight:700;color:#6b7280;background:#fff;transition:all .15s}
.pay-tab.on{border-color:var(--bg);color:var(--bg);background:#fef2f2}
.pay-pane{display:none}
.pay-pane.on{display:block}
.pkg-grid{display:flex;flex-direction:column;gap:10px;margin-bottom:16px}
.pkg-card{border:2px solid #e5e7eb;border-radius:14px;padding:14px 16px;cursor:pointer;transition:all .15s;position:relative}
.pkg-card:hover{border-color:#d1d5db}
.pkg-card.sel{border-color:var(--bg);background:#fff5f5}
.pkg-card.rec::before{content:"Выгоднее всего";position:absolute;top:-1px;right:14px;background:var(--bg);color:#fff;font-size:10px;font-weight:700;padding:2px 8px;border-radius:0 0 6px 6px}
.pkg-top{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:3px}
.pkg-name{font-size:14px;font-weight:700;color:var(--ink)}
.pkg-price{font-size:18px;font-weight:800;color:var(--bg)}
.pkg-desc{font-size:12px;color:var(--mut)}
.pkg-per{font-size:11px;color:#9ca3af;margin-top:2px}
.sub-grid{display:flex;flex-direction:column;gap:10px;margin-bottom:16px}
.sub-card{border:2px solid #e5e7eb;border-radius:14px;padding:14px 16px;cursor:pointer;transition:all .15s;position:relative}
.sub-card:hover{border-color:#d1d5db}
.sub-card.sel{border-color:var(--bg);background:#fff5f5}
.sub-card.rec::before{content:"Популярно";position:absolute;top:-1px;right:14px;background:var(--bg);color:#fff;font-size:10px;font-weight:700;padding:2px 8px;border-radius:0 0 6px 6px}
.sub-top{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:3px}
.sub-name{font-size:14px;font-weight:700;color:var(--ink)}
.sub-price{font-size:18px;font-weight:800;color:var(--bg)}
.sub-period{font-size:11px;color:#9ca3af;margin-left:3px}
.sub-desc{font-size:12px;color:var(--mut)}
.sub-feat{font-size:11px;color:#6b7280;margin-top:6px;display:flex;flex-wrap:wrap;gap:4px}
.sub-feat span{background:#f3f4f6;padding:2px 7px;border-radius:6px}
.pay-refund-note{font-size:11px;color:var(--mut);text-align:center;margin-bottom:12px}
.pay-refund-note a{color:var(--bg);text-decoration:none}
/* ── ВЫБОР ТИПА КЛИЕНТА ── */
.client-type-block{margin:20px 0;background:var(--card);border:1.5px solid var(--line);border-radius:14px;overflow:hidden}
.client-type-hdr{padding:14px 16px 10px;font-size:13px;font-weight:700;color:var(--dark)}
.client-type-tabs{display:grid;grid-template-columns:1fr 1fr 1fr;gap:0;border-top:1px solid var(--line)}
.ct-tab{padding:12px 8px;text-align:center;font-size:13px;font-weight:600;cursor:pointer;color:var(--mut);border-right:1px solid var(--line);transition:all .15s;user-select:none}
.ct-tab:last-child{border-right:none}
.ct-tab.act{background:var(--bg);color:#fff}
.ct-tab .ct-tab-ico{font-size:18px;display:block;margin-bottom:3px}
/* B2B форма */
.b2b-form{display:none;padding:16px;border-top:1px solid var(--line)}
.b2b-form.on{display:block}
.b2b-row{display:grid;gap:10px;margin-bottom:0}
.b2b-row.two{grid-template-columns:1fr 1fr}
.b2b-field{display:flex;flex-direction:column;gap:5px}
.b2b-field label{font-size:12px;font-weight:600;color:var(--mut);text-transform:uppercase;letter-spacing:.5px}
.b2b-field input{border:1.5px solid var(--line);border-radius:9px;padding:10px 12px;font-size:14px;font-family:inherit;outline:none;transition:border-color .15s}
.b2b-field input:focus{border-color:var(--bg)}
.b2b-field input.err{border-color:#9f1239}
.b2b-hint{font-size:11px;color:var(--mut);margin-top:2px}
.b2b-divider{margin:14px 0;border:none;border-top:1px solid var(--line)}
/* Загрузка документов B2B */
.b2b-docs-label{font-size:12px;font-weight:700;color:var(--dark);margin:14px 0 8px;text-transform:uppercase;letter-spacing:.5px}
.b2b-docs-note{font-size:12px;color:var(--mut);margin-bottom:10px}
.b2b-upload-area{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.b2b-upload-btn{border:1.5px dashed var(--line);border-radius:12px;padding:14px 10px;text-align:center;cursor:pointer;background:#fafafa;transition:all .15s;position:relative;overflow:hidden}
.b2b-upload-btn:hover{border-color:var(--bg);background:var(--tint)}
.b2b-upload-btn input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%}
.b2b-upload-btn .ubtn-ico{font-size:22px;display:block;margin-bottom:5px}
.b2b-upload-btn .ubtn-lbl{font-size:12px;font-weight:600;color:var(--dark)}
.b2b-upload-btn .ubtn-sub{font-size:11px;color:var(--mut);margin-top:2px}
.b2b-upload-btn.has-file{border-color:var(--ok);border-style:solid;background:#f0fdf4}
.b2b-upload-btn.has-file .ubtn-lbl{color:var(--ok)}
.b2b-files-list{margin-top:8px}
.b2b-file-chip{display:inline-flex;align-items:center;gap:6px;background:#f3f4f6;border-radius:8px;padding:4px 8px;font-size:12px;margin:3px 3px 0 0}
.b2b-file-chip .rm{cursor:pointer;color:var(--mut);font-size:11px;margin-left:2px}
.b2b-file-chip .rm:hover{color:#9f1239}
/* Camera preview */
.b2b-camera-wrap{display:none;margin-top:10px}
.b2b-camera-wrap.on{display:block}
.b2b-video{width:100%;border-radius:10px;background:#000;max-height:240px;object-fit:cover}
.b2b-camera-controls{display:flex;gap:8px;margin-top:8px}
.b2b-snap-btn{flex:1;background:var(--bg);color:#fff;border:none;border-radius:9px;padding:10px;font-size:13px;font-weight:700;cursor:pointer}
.b2b-cancel-btn{background:#f3f4f6;color:var(--dark);border:none;border-radius:9px;padding:10px 14px;font-size:13px;cursor:pointer}
.b2b-canvas{display:none}
.b2b-photo-preview{display:none;margin-top:8px;position:relative}
.b2b-photo-preview img{width:100%;border-radius:10px;border:1.5px solid var(--ok)}
.b2b-photo-preview .rm-photo{position:absolute;top:6px;right:6px;background:rgba(0,0,0,.5);color:#fff;border:none;border-radius:50%;width:24px;height:24px;font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center}
/* ЮKassa modal */
.yk-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;align-items:flex-end;justify-content:center}
.yk-overlay.on{display:flex}
.yk-modal{background:#fff;border-radius:20px 20px 0 0;padding:24px;width:100%;max-width:480px;animation:slideUp .25s ease}
@keyframes slideUp{from{transform:translateY(100%)}to{transform:translateY(0)}}
.yk-hdr{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
.yk-ttl{font-size:16px;font-weight:800;color:var(--ink)}
.yk-close{font-size:20px;cursor:pointer;color:#9ca3af;background:none;border:none;padding:4px}
.yk-amount{font-size:28px;font-weight:800;color:var(--bg);text-align:center;margin-bottom:20px}
.yk-methods{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:18px}
.yk-method{border:2px solid #e5e7eb;border-radius:12px;padding:12px;cursor:pointer;text-align:center;transition:all .15s}
.yk-method:hover,.yk-method.sel{border-color:var(--bg);background:#fff5f5}
.yk-method-ico{font-size:22px;margin-bottom:4px}
.yk-method-lbl{font-size:11px;font-weight:700;color:var(--ink)}
.yk-sbp{background:linear-gradient(135deg,#00b27a,#00913c);color:#fff;border:none!important}
.yk-sbp .yk-method-lbl{color:#fff}
.yk-card-form{margin-bottom:16px}
.yk-card-form input{width:100%;border:1.5px solid #e5e7eb;border-radius:10px;padding:10px 12px;font-size:14px;box-sizing:border-box;margin-bottom:8px}
.yk-card-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.yk-pay-btn{width:100%;background:var(--bg);color:#fff;border:none;border-radius:12px;padding:14px;font-size:15px;font-weight:700;cursor:pointer}
.yk-pay-btn:hover{background:var(--bghv)}
.yk-secure{font-size:10px;color:#9ca3af;text-align:center;margin-top:8px}.pdn a{color:var(--bg)}
/* ── кабинет (общий каркас) ── */
.app{display:flex;min-height:100vh;background:#f4f5f7}
/* ── Сайдбар ── */
.side{width:224px;background:#0C0608;color:#cbb;display:flex;flex-direction:column;flex-shrink:0;min-height:100vh}
.side-logo{padding:20px 20px 16px;border-bottom:1px solid rgba(255,255,255,.07)}
.side-logo img{height:16px;filter:brightness(0) invert(1)}
.side-nav{padding:10px 0;flex:1}
.side-section{font-size:10px;font-weight:700;letter-spacing:.8px;color:rgba(255,255,255,.25);padding:14px 20px 5px;text-transform:uppercase}
.side a{display:flex;align-items:center;gap:10px;padding:9px 20px;color:rgba(255,255,255,.55);font-size:13px;font-weight:500;cursor:pointer;border-left:3px solid transparent;transition:all .15s;text-decoration:none}
.side a:hover{color:#fff;background:rgba(255,255,255,.05)}
.side a.on{color:#fff;background:rgba(159,18,57,.25);border-left-color:var(--bg)}
.side a .sico{font-size:15px;width:20px;text-align:center;flex-shrink:0}
.side a .sbadge{margin-left:auto;background:var(--bg);color:#fff;font-size:10px;font-weight:700;border-radius:10px;padding:1px 6px;min-width:18px;text-align:center}
.side-new{margin:8px 12px 4px;background:var(--bg);color:#fff;border:none;border-radius:10px;padding:10px 14px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;width:calc(100% - 24px);display:flex;align-items:center;gap:8px;transition:background .15s}
.side-new:hover{background:var(--bghv)}
.side .prof{padding:14px 16px;border-top:1px solid rgba(255,255,255,.07);display:flex;gap:10px;align-items:center}
.side .prof .pa{width:34px;height:34px;border-radius:10px;background:var(--bg);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:12px;flex-shrink:0}
.side .prof .pn{font-size:12px;color:#fff;font-weight:600}
.side .prof .pt{font-size:10px;color:rgba(255,255,255,.4);margin-top:1px}
/* ── Основная область ── */
.main{flex:1;display:flex;flex-direction:column;min-width:0;min-height:100vh}
.main-hdr{background:#fff;border-bottom:1px solid #e5e7eb;padding:0 28px;height:56px;display:flex;align-items:center;gap:16px;flex-shrink:0;position:sticky;top:0;z-index:50}
.main-hdr h2{font-size:17px;font-weight:800;margin:0;flex-shrink:0}
.main-hdr-search{flex:1;max-width:340px;position:relative}
.main-hdr-search input{width:100%;border:1.5px solid #e5e7eb;border-radius:9px;padding:7px 12px 7px 34px;font-size:13px;font-family:inherit;outline:none;background:#f9fafb;color:var(--ink);box-sizing:border-box}
.main-hdr-search input:focus{border-color:var(--bg);background:#fff}
.main-hdr-search::before{content:'🔍';position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:13px;pointer-events:none}
.main-hdr-actions{margin-left:auto;display:flex;gap:8px;align-items:center}
.main-body{padding:24px 28px;flex:1}
.main-body .crumb{font-size:12px;color:var(--mut);margin-bottom:6px}
.main-body h1{font-size:20px;font-weight:800;margin:0 0 18px}
/* ── KPI карточки ── */
.kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:22px}
.kpi-card{background:#fff;border:1px solid #e5e7eb;border-radius:13px;padding:16px 18px;display:flex;align-items:center;gap:14px}
.kpi-card .kc-ico{width:42px;height:42px;border-radius:11px;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.kpi-card .kc-num{font-size:24px;font-weight:800;line-height:1}
.kpi-card .kc-lbl{font-size:12px;color:var(--mut);margin-top:2px}
.kpi-card.kpi-total .kc-ico{background:#ede9fe}
.kpi-card.kpi-work .kc-ico{background:#dbeafe}
.kpi-card.kpi-urg .kc-ico{background:#fee2e2}
.kpi-card.kpi-done .kc-ico{background:#dcfce7}
.enote{display:flex;gap:11px;align-items:center;background:var(--card);border:1px solid var(--line);border-left:3px solid var(--bg);border-radius:12px;padding:12px 14px;max-width:760px;margin:14px 0 16px}
.enote img{width:40px;height:40px;border-radius:50%;object-fit:cover;object-position:center 16%}
.enote .et{font-size:13px}
.cases{display:flex;flex-direction:column;gap:11px;max-width:760px}
.case{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px;display:flex;align-items:center;gap:13px;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.case:hover{border-color:var(--bg);transform:translateY(-1px);box-shadow:0 6px 18px rgba(0,0,0,.08)}
.case .ci{width:44px;height:44px;border-radius:11px;background:var(--tint);display:flex;align-items:center;justify-content:center;font-size:21px}
.case .cb{flex:1}.case .ct{font-weight:700;font-size:14px}.case .cs{font-size:12px;color:var(--mut);margin-top:2px}
.case .cm{display:flex;gap:6px;margin-top:6px;flex-wrap:wrap}.case .cg{color:var(--bg);font-weight:700;font-size:13px}
.tabpane{display:none}.tabpane.on{display:block}
.docrow{display:flex;align-items:center;gap:9px;font-size:13px;padding:9px 0;border-top:1px dashed var(--line);max-width:760px}
.docrow:first-of-type{border-top:none}.docrow .ver{font-size:10px;font-weight:700;color:var(--bg);background:var(--tint);border-radius:6px;padding:2px 6px;margin-left:auto}
.dlx{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:12px 15px;display:flex;align-items:center;gap:13px;max-width:760px;margin-bottom:9px}
.dlx .dd{min-width:50px;height:50px;border-radius:11px;display:flex;flex-direction:column;align-items:center;justify-content:center;font-weight:800}
.dd.dd{background:var(--dngbg);color:var(--dng)}.dd.dw{background:var(--warnbg);color:var(--warn)}.dd.dn{background:var(--surf);color:var(--mut)}
.dlx .n{font-size:18px;line-height:1}.dlx .u{font-size:9px}
.tpls{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;max-width:760px}
.tpl{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:15px;cursor:pointer}.tpl:hover{border-color:var(--bg)}
.tpl .ti{font-size:22px}.tpl .tn{font-weight:700;font-size:13.5px;margin:7px 0 3px}.tpl .td{font-size:12px;color:var(--mut)}
.steps{font-size:12px;color:var(--mut);margin:24px 0;display:flex;gap:8px;flex-wrap:wrap}
.steps b{color:var(--bg)}
/* ── выбор deliverable ── */
.deliverables{display:flex;flex-direction:column;gap:9px;margin:4px 0 4px 51px;max-width:520px}
.deliv{background:var(--card);border:1.5px solid var(--line);border-radius:13px;padding:13px 16px;cursor:pointer;display:flex;align-items:flex-start;gap:12px;transition:border-color .15s,box-shadow .15s}
.deliv:hover{border-color:var(--bg);box-shadow:0 4px 14px rgba(159,18,57,.1)}
.deliv .di{font-size:20px;flex-shrink:0;margin-top:1px}
.deliv .dn{font-size:13.5px;font-weight:700}
.deliv .dd2{font-size:12.5px;color:var(--mut);margin-top:4px;line-height:1.55}
.deliv-top{border-color:rgba(159,18,57,.3);background:var(--tint)}
.deliv-top .dn{color:var(--bg)}
.deliv-badge{font-size:10px;font-weight:700;background:var(--bg);color:#fff;border-radius:6px;padding:2px 7px;margin-left:auto;flex-shrink:0}
.deliv-highlighted{border-color:var(--bg)!important;background:linear-gradient(135deg,#fff 0%,rgba(159,18,57,.06) 100%)!important;box-shadow:0 0 0 2px rgba(159,18,57,.15)!important}
.deliv-highlighted .dn{color:var(--bg)!important}
.ctype-note{display:block;font-size:13px;color:var(--mut);line-height:1.5;margin-bottom:6px;padding:8px 10px;background:var(--surf);border-radius:8px;border-left:3px solid var(--bg)}
.sample-link{display:inline-block;margin-top:7px;font-size:11px;font-weight:600;color:var(--bg);text-decoration:none;border:1px solid rgba(159,18,57,.3);border-radius:5px;padding:2px 9px;transition:background .15s}
.sample-link:hover{background:var(--tint)}
.deliv-sep{font-size:11px;color:var(--mut);text-align:center;padding:6px 0 2px;letter-spacing:.3px;display:flex;align-items:center;gap:8px}
.deliv-sep::before,.deliv-sep::after{content:'';flex:1;height:1px;background:var(--line)}
/* ── свой запрос ── */
.custom-req-row{margin:14px 0 0 51px}
.custom-req-toggle{background:none;border:1.5px dashed var(--line);border-radius:11px;padding:9px 14px;font-size:13px;color:var(--mut);cursor:pointer;font-family:inherit;transition:all .15s;width:100%;max-width:520px;text-align:left}
.custom-req-toggle:hover{border-color:var(--bg);color:var(--bg);background:var(--tint)}
.custom-req-area{margin-top:4px}
.custom-input-block{margin:0 0 12px 51px;max-width:520px}
.custom-input-block textarea{width:100%;border:1.5px solid var(--line);border-radius:11px;padding:11px 13px;font-size:14px;font-family:inherit;color:var(--ink);resize:none;background:var(--card);transition:border .15s;line-height:1.5}
.custom-input-block textarea:focus{outline:none;border-color:var(--bg)}
.custom-input-btns{display:flex;gap:8px;margin-top:8px;align-items:stretch}
.custom-voice-btn{background:var(--surf);border:1.5px solid var(--line);border-radius:11px;width:46px;height:46px;font-size:20px;cursor:pointer;flex-shrink:0;transition:all .15s;display:flex;align-items:center;justify-content:center}
.custom-voice-btn:hover{border-color:var(--bg);background:var(--tint)}
.custom-voice-btn.recording{background:#fee2e2;border-color:#ef4444;animation:crpulse 1s infinite}
@keyframes crpulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,.3)}50%{box-shadow:0 0 0 6px rgba(239,68,68,0)}}
/* ── план после выбора ── */
.plan-what{background:var(--surf);border-radius:12px;padding:14px 16px;margin:16px 0;max-width:600px}
.plan-what-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;color:var(--mut);margin-bottom:10px}
.plan-what li{font-size:13px;color:var(--dark);padding:4px 0;list-style:none;display:flex;gap:8px;align-items:flex-start}
.plan-what li::before{content:'✓';color:var(--ok);font-weight:700;flex-shrink:0}
.plan-pitch{background:var(--tint);border:1.5px solid rgba(159,18,57,.2);border-radius:13px;padding:14px 16px;margin:20px 0;display:flex;gap:11px;align-items:flex-start;max-width:600px}
.plan-pitch img{width:38px;height:38px;border-radius:50%;object-fit:cover;object-position:center 16%;flex-shrink:0}
.plan-pitch-body{font-size:13px;line-height:1.6}
.plan-pitch-body b{color:var(--bg)}
.plan-pitch-body .pi-step{display:flex;gap:10px;margin:5px 0;align-items:flex-start;font-size:13px;line-height:1.6}
.plan-pitch-body .pi-n{background:var(--bg);color:#fff;border-radius:50%;width:20px;height:20px;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px}
.plan-pitch-body .pi-meta{font-size:11.5px;color:var(--mut);background:var(--surf);border-radius:7px;padding:5px 10px;margin-top:8px;line-height:1.5}
.plan-pitch-body .pi-tag{display:inline-block;font-size:10px;font-weight:700;padding:2px 7px;border-radius:8px;margin-right:5px;margin-top:2px}
.plan-pitch-body .pi-incl{background:var(--okbg);color:var(--ok)}.plan-pitch-body .pi-warn{background:var(--warnbg);color:var(--warn)}
/* ── intake-вопрос ── */
.intake{display:flex;flex-direction:column;gap:9px;margin:4px 0 4px 51px}
.intake-opt{background:var(--card);border:1.5px solid var(--line);border-radius:13px;padding:12px 16px;cursor:pointer;font-size:13.5px;font-weight:600;display:flex;align-items:center;gap:10px;transition:border-color .15s,box-shadow .15s}
.intake-opt:hover{border-color:var(--bg);box-shadow:0 4px 14px rgba(159,18,57,.1)}
.intake-opt .io{font-size:18px;pointer-events:none}
.intake-opt .id{font-weight:400;color:var(--mut);font-size:12px;margin-top:2px;pointer-events:none}
.intake-opt>div{pointer-events:none}
.intake-other{display:flex;gap:8px;margin-top:2px}
.intake-other input{flex:1;border:1.5px solid var(--line);border-radius:11px;padding:10px 13px;font-size:13.5px;font-family:inherit;outline:none}
.intake-other input:focus{border-color:var(--bg)}
/* цитата из договора */
.risk-quote{background:var(--surf);border-left:3px solid var(--bg);border-radius:0 8px 8px 0;padding:7px 10px;font-size:12px;color:var(--mut);font-style:italic;margin:6px 0 5px;line-height:1.5}
.intake-other button{background:var(--bg);color:#fff;border:none;border-radius:11px;padding:10px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}
/* режим — полоска под topbar */
.mode-badge{font-size:11px;font-weight:700;padding:4px 12px;border-radius:0 0 10px 10px;display:inline-block;margin-left:22px;letter-spacing:.5px}
.mode-novice{background:rgba(159,18,57,.1);color:var(--bg)}
.mode-mid{background:rgba(245,158,11,.12);color:#92400E}
.mode-pro{background:rgba(16,185,129,.1);color:#065F46}
/* stats dev-panel */
.stats-pill{position:fixed;bottom:70px;right:18px;background:#0C0608;color:#fff;border-radius:12px;padding:10px 14px;font-size:11px;z-index:9999;cursor:pointer;line-height:1.6;min-width:160px;box-shadow:0 6px 20px rgba(0,0,0,.25)}
.stats-pill b{color:#FECDD3}
/* ── GANTT ── */
.gantt-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;margin-top:4px;padding-bottom:16px}
/* ── DEADLINE CARDS ── */
.dl-filter{display:flex;gap:8px;margin-bottom:20px;flex-wrap:wrap}
.dl-ftab{padding:6px 16px;border-radius:20px;border:1.5px solid #e5e7eb;font-size:12px;font-weight:600;cursor:pointer;color:#6b7280;background:#fff;transition:all .15s}
.dl-ftab.on{background:var(--bg);color:#fff;border-color:var(--bg)}
.dl-list{display:flex;flex-direction:column;gap:10px}
.dl-card{background:#fff;border:1.5px solid #e5e7eb;border-radius:14px;padding:16px 18px;display:flex;gap:14px;align-items:flex-start;transition:box-shadow .15s}
.dl-card:hover{box-shadow:0 2px 12px rgba(0,0,0,.07)}
.dl-card.overdue{border-left:4px solid #ef4444}
.dl-card.soon{border-left:4px solid #f59e0b}
.dl-card.ok{border-left:4px solid #22c55e}
.dl-card.done{opacity:.55}
.dl-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;margin-top:4px}
.dl-card.overdue .dl-dot{background:#ef4444}
.dl-card.soon .dl-dot{background:#f59e0b}
.dl-card.ok .dl-dot{background:#22c55e}
.dl-card.done .dl-dot{background:#9ca3af}
.dl-body{flex:1;min-width:0}
.dl-title{font-size:14px;font-weight:700;color:var(--ink);margin-bottom:3px}
.dl-meta{font-size:12px;color:var(--mut);margin-bottom:6px}
.dl-quote{font-size:11px;color:#6b7280;background:#f9fafb;border-left:2px solid #e5e7eb;padding:5px 10px;border-radius:0 6px 6px 0;margin-bottom:8px;line-height:1.5}
.dl-right{flex-shrink:0;text-align:right}
.dl-date{font-size:12px;font-weight:700;color:var(--ink);margin-bottom:4px}
.dl-badge{font-size:11px;font-weight:700;padding:2px 8px;border-radius:6px}
.dl-badge.overdue{background:#fee2e2;color:#991b1b}
.dl-badge.soon{background:#fef3c7;color:#92400e}
.dl-badge.ok{background:#dcfce7;color:#166534}
.dl-badge.done{background:#f3f4f6;color:#6b7280}
.dl-btn{margin-top:8px;font-size:11px;font-weight:700;padding:5px 12px;border-radius:8px;border:1.5px solid #e5e7eb;background:#fff;cursor:pointer;color:#374151;transition:all .15s;white-space:nowrap}
.dl-btn:hover{border-color:var(--bg);color:var(--bg)}
.dl-btn.done-btn{border-color:#22c55e;color:#16a34a}
.dl-empty{text-align:center;padding:48px 24px;color:var(--mut)}
.dl-empty-ico{font-size:40px;margin-bottom:12px}
.dl-empty-txt{font-size:14px;font-weight:600;margin-bottom:6px;color:var(--ink)}
.dl-empty-sub{font-size:12px}
/* ── ADMIN SCREEN ── */
.admin-wrap{padding:24px 20px;max-width:900px;margin:0 auto}
.admin-kpi{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:24px}
@media(max-width:600px){.admin-kpi{grid-template-columns:1fr 1fr}}
.akpi{background:#fff;border:1.5px solid #e5e7eb;border-radius:14px;padding:14px 16px}
.akpi-num{font-size:24px;font-weight:800;line-height:1;margin-bottom:4px}
.akpi-lbl{font-size:11px;color:var(--mut);font-weight:600}
.akpi-trend{font-size:11px;margin-top:3px}
.akpi-trend.up{color:#16a34a}
.akpi-trend.dn{color:#dc2626}
.admin-section{margin-bottom:24px}
.admin-section-hdr{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.admin-section-ttl{font-size:15px;font-weight:800;color:var(--ink)}
.admin-section-link{font-size:12px;color:var(--bg);cursor:pointer;font-weight:600}
.pay-table{width:100%;border-collapse:collapse;background:#fff;border-radius:12px;overflow:hidden;border:1.5px solid #e5e7eb}
.pay-table th{background:#f9fafb;font-size:11px;font-weight:700;color:var(--mut);text-transform:uppercase;padding:8px 12px;text-align:left}
.pay-table td{padding:10px 12px;font-size:13px;border-top:1px solid #f3f4f6;color:var(--ink)}
.pay-table tr:hover td{background:#fafafa}
.pay-badge{font-size:10px;font-weight:700;padding:2px 7px;border-radius:6px}
.pay-badge.paid{background:#dcfce7;color:#166534}
.pay-badge.ref{background:#fee2e2;color:#991b1b}
.pay-badge.pend{background:#fef3c7;color:#92400e}
.refund-card{background:#fff;border:1.5px solid #e5e7eb;border-radius:12px;padding:14px 16px;margin-bottom:8px;display:flex;gap:12px;align-items:flex-start}
.refund-card.urgent{border-left:3px solid #ef4444}
.refund-body{flex:1}
.refund-ttl{font-size:13px;font-weight:700;color:var(--ink);margin-bottom:3px}
.refund-meta{font-size:11px;color:var(--mut)}
.refund-amt{font-size:14px;font-weight:800;color:#ef4444;flex-shrink:0;text-align:right}
.refund-actions{display:flex;gap:6px;margin-top:8px}
.refund-btn{font-size:11px;font-weight:700;padding:5px 10px;border-radius:7px;border:1.5px solid;cursor:pointer}
.refund-btn.approve{border-color:#16a34a;color:#16a34a;background:#f0fdf4}
.refund-btn.decline{border-color:#dc2626;color:#dc2626;background:#fef2f2}
.admin-chart{background:#fff;border:1.5px solid #e5e7eb;border-radius:14px;padding:16px;margin-bottom:24px}
.chart-bars{display:flex;align-items:flex-end;gap:3px;height:80px;margin-top:8px}
.chart-bar{flex:1;border-radius:4px 4px 0 0;min-height:4px;transition:height .3s}
.chart-bar.cur{background:var(--bg)}
.chart-bar:not(.cur){background:#fecaca}
.chart-lbl{display:flex;gap:3px;margin-top:4px}
.chart-lbl span{flex:1;font-size:9px;color:var(--mut);text-align:center;overflow:hidden}
/* Balance tab */
.bal-hero{background:linear-gradient(135deg,#4f46e5,#7c3aed);border-radius:16px;padding:20px;color:#fff;margin-bottom:20px;text-align:center}
.bal-hero-num{font-size:48px;font-weight:800;line-height:1}
.bal-hero-lbl{font-size:13px;opacity:.8;margin-top:4px}
.bal-hero-sub{font-size:12px;opacity:.65;margin-top:6px}
.bal-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:20px}
.bal-action-btn{padding:12px;border:2px solid #e5e7eb;border-radius:12px;text-align:center;cursor:pointer;font-size:13px;font-weight:700;color:var(--ink);background:#fff;transition:all .15s}
.bal-action-btn:hover{border-color:var(--bg);color:var(--bg)}
.pay-hist-item{display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #f3f4f6}
.phi-left{}
.phi-desc{font-size:13px;font-weight:600;color:var(--ink)}
.phi-date{font-size:11px;color:var(--mut);margin-top:2px}
.phi-right{text-align:right;flex-shrink:0}
.phi-amt{font-size:14px;font-weight:700}
.phi-amt.cr{color:#16a34a}
.phi-amt.db{color:#ef4444}
.phi-status{font-size:10px;color:var(--mut);margin-top:2px}
.ref-form{background:#fff5f5;border:1.5px solid #fecaca;border-radius:12px;padding:14px;margin-top:16px}
.ref-form h4{font-size:13px;font-weight:700;color:var(--ink);margin-bottom:10px}
.ref-form textarea{width:100%;border:1.5px solid #e5e7eb;border-radius:8px;padding:8px;font-size:13px;box-sizing:border-box;height:70px;resize:none;margin-bottom:8px}
.ref-form-hint{font-size:11px;color:var(--mut);margin-bottom:10px}
.dl-summary{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap}
.dl-sum-card{background:#fff;border:1.5px solid #e5e7eb;border-radius:12px;padding:12px 16px;flex:1;min-width:100px;text-align:center}
.dl-sum-num{font-size:22px;font-weight:800;line-height:1}
.dl-sum-lbl{font-size:11px;color:var(--mut);margin-top:3px}
.dl-sum-card.red .dl-sum-num{color:#ef4444}
.dl-sum-card.yellow .dl-sum-num{color:#f59e0b}
.dl-sum-card.green .dl-sum-num{color:#22c55e}
.gantt{min-width:580px;font-family:var(--font-ui)}
.gantt-hdr{display:grid;grid-template-columns:170px 1fr;align-items:end;margin-bottom:6px;padding-bottom:8px;border-bottom:1.5px solid var(--line)}
.gantt-hdr-lbl{font-size:11px;font-weight:700;color:var(--mut);text-transform:uppercase;letter-spacing:.5px}
.gantt-dates{position:relative;height:22px}
.gantt-tick{position:absolute;transform:translateX(-50%);font-size:10px;color:var(--mut);font-weight:500;white-space:nowrap}
.gantt-today-hdr{position:absolute;transform:translateX(-50%);font-size:10px;font-weight:800;color:var(--bg);white-space:nowrap;top:-2px}
.gantt-row{display:grid;grid-template-columns:170px 1fr;align-items:center;margin-bottom:8px;cursor:pointer}
.gantt-row:hover .gantt-lbl{color:var(--bg)}
.gantt-lbl{padding-right:12px;overflow:hidden}
.gantt-lbl-name{font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.gantt-lbl-sub{font-size:11px;color:var(--mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.gantt-track{position:relative;height:32px;background:var(--surf);border-radius:6px;overflow:visible}
.gantt-bar{position:absolute;height:100%;border-radius:6px;display:flex;align-items:center;padding:0 10px;font-size:11px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.18);transition:filter .15s}
.gantt-bar:hover{filter:brightness(1.1)}
.gantt-bar.g-overdue{background:linear-gradient(90deg,#7f1d1d,#dc2626)}
.gantt-bar.g-urgent{background:linear-gradient(90deg,#9f1239,#e11d48)}
.gantt-bar.g-warn{background:linear-gradient(90deg,#92400e,#d97706)}
.gantt-bar.g-ok{background:linear-gradient(90deg,#1e3a8a,#3b82f6)}
.gantt-bar.g-done{background:linear-gradient(90deg,#14532d,#16a34a);opacity:.7}
.gantt-today-line{position:absolute;top:-4px;bottom:-4px;width:2px;background:var(--bg);border-radius:1px;z-index:10;pointer-events:none}
.gantt-today-line::after{content:'';position:absolute;top:0;left:50%;transform:translateX(-50%);width:8px;height:8px;background:var(--bg);border-radius:50%;margin-top:-3px}
.gantt-legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:16px;padding-top:12px;border-top:1px solid var(--line)}
.gantt-leg-item{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--mut)}
.gantt-leg-dot{width:10px;height:10px;border-radius:3px;flex-shrink:0}
/* ── GANTT ── */
(function() {
// Дата-хелперы
function d(str) { return new Date(str + 'T00:00:00'); }
function addDays(dt, n) { var r = new Date(dt); r.setDate(r.getDate()+n); return r; }
function daysBetween(a, b) { return Math.round((b-a)/86400000); }
function fmt(dt) {
var m = ['янв','фев','мар','апр','май','июн','июл','авг','сен','окт','ноя','дек'];
return dt.getDate() + ' ' + m[dt.getMonth()];
}
var CASES = [
{ ico:'🍽', name:'Кухня агентский', sub:'Протокол разногласий · ст.22 ЗоЗПП', start:'2025-05-19', end:'2025-05-26', cls:'g-urgent', go:"tab('case')" },
{ ico:'💼', name:'Трудовой договор', sub:'Допсоглашение (испыт. срок)', start:'2025-05-21', end:'2025-05-30', cls:'g-warn', go:"tab('case')" },
{ ico:'🏠', name:'ДДУ (новая редакция)',sub:'Сверка версий с застройщиком', start:'2025-05-19', end:'2025-06-05', cls:'g-ok', go:"tab('case')" },
{ ico:'📄', name:'Квартира · ДДУ', sub:'Передача квартиры · 214-ФЗ', start:'2025-05-27', end:'2025-06-22', cls:'g-ok', go:"toast('📅 Открываю дело')" },
];
function render() {
var root = document.getElementById('gantt-root');
if (!root) return;
var today = new Date(); today.setHours(0,0,0,0);
var rangeStart = d('2025-05-17');
var rangeEnd = d('2025-06-25');
var totalDays = daysBetween(rangeStart, rangeEnd);
function pct(dt) { return Math.max(0, Math.min(100, daysBetween(rangeStart, dt) / totalDays * 100)); }
// Шапка с датами
var tickDates = [];
var cur = new Date(rangeStart);
while (cur <= rangeEnd) {
tickDates.push(new Date(cur));
cur.setDate(cur.getDate() + 7);
}
var ticksHTML = tickDates.map(function(dt) {
var p = pct(dt);
return '<span class="gantt-tick" style="left:' + p.toFixed(1) + '%">' + fmt(dt) + '</span>';
}).join('');
var todayPct = pct(today);
var todayHdrHTML = '<span class="gantt-today-hdr" style="left:' + todayPct.toFixed(1) + '%">сегодня </span>';
var hdrHTML = '<div class="gantt-hdr"><div class="gantt-hdr-lbl">Договор / дело</div><div class="gantt-dates">' + ticksHTML + todayHdrHTML + '</div></div>';
// Строки
var rowsHTML = CASES.map(function(c) {
var startPct = pct(d(c.start));
var endPct = pct(d(c.end));
var width = Math.max(4, endPct - startPct);
var daysLeft = daysBetween(today, d(c.end));
var lbl = daysLeft < 0 ? ' просрочен' : daysLeft === 0 ? 'сегодня' : daysLeft + ' дн.';
var cls = c.cls;
if (daysLeft < 0) cls = 'g-overdue';
var todayLine = '<div class="gantt-today-line" style="left:' + todayPct.toFixed(1) + '%"></div>';
return '<div class="gantt-row" onclick="' + c.go + '">' +
'<div class="gantt-lbl"><div class="gantt-lbl-name">' + c.ico + ' ' + c.name + '</div><div class="gantt-lbl-sub">' + c.sub + '</div></div>' +
'<div class="gantt-track">' +
todayLine +
'<div class="gantt-bar ' + cls + '" style="left:' + startPct.toFixed(1) + '%;width:' + width.toFixed(1) + '%">' + fmt(d(c.end)) + ' · ' + lbl + '</div>' +
'</div>' +
'</div>';
}).join('');
// Легенда
var legend = '<div class="gantt-legend">' +
'<div class="gantt-leg-item"><div class="gantt-leg-dot" style="background:#dc2626"></div>Просрочен</div>' +
'<div class="gantt-leg-item"><div class="gantt-leg-dot" style="background:#e11d48"></div>Срочно (3 дня)</div>' +
'<div class="gantt-leg-item"><div class="gantt-leg-dot" style="background:#d97706"></div>Скоро (7 дней)</div>' +
'<div class="gantt-leg-item"><div class="gantt-leg-dot" style="background:#3b82f6"></div>В работе</div>' +
'<div class="gantt-leg-item"><div class="gantt-leg-dot" style="background:#16a34a;opacity:.7"></div>Завершён</div>' +
'</div>';
root.innerHTML = hdrHTML + rowsHTML + legend;
}
// Запускаем при открытии вкладки
var _origTab = window.tab;
window.tab = function(id) {
if (_origTab) _origTab(id);
if (id === 'sroki') setTimeout(render, 50);
};
window.addEventListener('DOMContentLoaded', function() {
// Если уже на вкладке sroki
var el = document.getElementById('p-sroki');
if (el && el.classList.contains('on')) render();
});
})();
/* ── ТАБЛИЦА ДОГОВОРОВ ── */
.ct-filters{display:flex;align-items:center;gap:0;margin-bottom:18px;background:#fff;border:1.5px solid #e5e7eb;border-radius:10px;padding:4px;width:fit-content}
.ct-filter-sep{width:1px;height:20px;background:#e5e7eb;margin:0 4px;flex-shrink:0}
.ct-fbtn{background:transparent;border:none;border-radius:7px;padding:6px 14px;font-size:12px;font-weight:600;cursor:pointer;color:#6b7280;font-family:inherit;transition:all .15s;white-space:nowrap}
.ct-fbtn:hover{color:var(--ink);background:#f3f4f6}
.ct-fbtn.act{background:var(--bg);color:#fff;box-shadow:0 1px 4px rgba(159,18,57,.25)}
.ct-add{margin-left:auto;background:var(--bg);color:#fff;border:none;border-radius:8px;padding:6px 14px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}
.ct-add:hover{background:var(--bghv)}
.ct-table{width:100%;border-collapse:collapse;font-size:13px}
.ct-table thead tr{background:#f3f4f6;border-bottom:2px solid var(--line)}
.ct-table thead th{padding:8px 10px;text-align:left;font-size:11px;font-weight:700;color:var(--mut);text-transform:uppercase;letter-spacing:.4px;cursor:pointer;white-space:nowrap;user-select:none}
.ct-table thead th:hover{color:var(--bg)}
.ct-table thead th.sort-asc::after{content:' '}
.ct-table thead th.sort-desc::after{content:' '}
.ct-table tbody tr{border-bottom:1px solid var(--line);cursor:pointer;transition:background .12s}
.ct-table tbody tr:hover{background:#f8f8ff}
.ct-table tbody tr.ct-closed{opacity:.55}
.ct-table tbody td{padding:9px 10px;white-space:nowrap;vertical-align:middle}
.ct-table tbody td.ct-name{font-weight:600;max-width:220px;overflow:hidden;text-overflow:ellipsis}
.ct-table tbody td.ct-open{color:var(--bg);font-weight:700;font-size:13px;text-align:right}
.ct-type-badge{background:var(--surf);border:1px solid var(--line);border-radius:6px;padding:2px 7px;font-size:11px;color:var(--mut);font-weight:500}
/* ── MS-PROJECT GANTT ── */
.msp-section{margin-top:28px;border-top:2px solid var(--line);padding-top:16px}
.msp-title{font-size:13px;font-weight:800;color:var(--mut);text-transform:uppercase;letter-spacing:.6px;margin-bottom:12px}
.msp-outer{border:1.5px solid #d1d5db;border-radius:10px;overflow:hidden;font-size:12px;font-family:var(--font-ui)}
.msp-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
.msp-table{display:grid;min-width:820px}
/* Шапка */
.msp-head{display:flex;background:#e8e8f0;border-bottom:2px solid #b0b0c8;font-weight:700;font-size:11px;color:#444}
.msp-head .msp-left{display:flex;border-right:2px solid #b0b0c8;flex-shrink:0}
.msp-head .msp-right{flex:1;position:relative;overflow:hidden}
.msp-hcol{padding:6px 8px;border-right:1px solid #c8c8d8;white-space:nowrap;color:#333}
.msp-hcol.msp-c-num{width:32px;text-align:center}
.msp-hcol.msp-c-name{width:200px}
.msp-hcol.msp-c-dur{width:56px;text-align:center}
.msp-hcol.msp-c-start{width:72px;text-align:center}
.msp-hcol.msp-c-end{width:72px;text-align:center}
/* Строки */
.msp-row{display:flex;border-bottom:1px solid #e5e7eb;cursor:pointer}
.msp-row:hover{background:#f0f0ff}
.msp-row.msp-group{background:#f3f4f8;font-weight:700}
.msp-row.msp-done .msp-cell{color:#9ca3af}
.msp-row.msp-done .msp-c-name{text-decoration:line-through;color:#9ca3af}
.msp-row.msp-group.msp-done .msp-c-name{text-decoration:none;color:#6b7280}
.msp-bar.b-done{background:linear-gradient(180deg,#16a34a,#14532d)}
.msp-bar.b-done::after{content:' ';font-size:10px}
.msp-row.msp-active-row{background:#fffbf0}
.msp-row.msp-active-row:hover{background:#fff7e0}
.msp-bar.b-active::after{content:' '}
.msp-bar.b-urgent::after{content:' '}
.msp-soa{display:flex;align-items:center;gap:8px;margin-bottom:10px;font-size:12px;color:var(--mut)}
.msp-soa-item{display:flex;align-items:center;gap:4px}
.msp-soa-dot{width:12px;height:12px;border-radius:2px;flex-shrink:0}
.msp-elena-note{display:flex;align-items:flex-start;gap:8px;background:#fffbf0;border:1px solid #fde68a;border-radius:10px;padding:10px 14px;margin-bottom:12px;font-size:12px;color:#92400e}
.msp-elena-note img{width:28px;height:28px;border-radius:50%;object-fit:cover;object-position:center 16%;flex-shrink:0}
.msp-row.msp-group:hover{background:#ebebf8}
.msp-row:last-child{border-bottom:none}
.msp-left{display:flex;border-right:2px solid #b0b0c8;flex-shrink:0}
.msp-right{flex:1;position:relative;height:28px;overflow:hidden}
.msp-cell{padding:6px 8px;border-right:1px solid #e5e7eb;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center}
.msp-cell.msp-c-num{width:32px;justify-content:center;color:#888;font-size:10px}
.msp-cell.msp-c-name{width:200px}
.msp-cell.msp-c-name.ind{padding-left:20px;font-weight:400;color:#333}
.msp-cell.msp-c-dur{width:56px;justify-content:center;color:#555}
.msp-cell.msp-c-start{width:72px;justify-content:center;color:#555;font-size:11px}
.msp-cell.msp-c-end{width:72px;justify-content:center;color:#555;font-size:11px}
/* Сетка и бары */
.msp-grid-bg{position:absolute;inset:0;display:flex}
.msp-grid-col{flex:1;border-right:1px solid #e8e8f0;height:100%}
.msp-grid-col.weekend{background:#f5f5fb}
.msp-bar-wrap{position:absolute;inset:0;pointer-events:none}
.msp-bar{position:absolute;top:5px;height:18px;border-radius:3px;display:flex;align-items:center;padding:0 6px;font-size:10px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden}
.msp-bar.b-group{background:linear-gradient(180deg,#4a4a8a,#2d2d6a);height:8px;top:10px;border-radius:1px}
.msp-bar.b-done{background:linear-gradient(180deg,#2d7a2d,#1a5c1a)}
.msp-bar.b-active{background:linear-gradient(180deg,#1a56db,#1e40af)}
.msp-bar.b-urgent{background:linear-gradient(180deg,#c81e1e,#991b1b)}
.msp-bar.b-pending{background:linear-gradient(180deg,#6b7280,#4b5563)}
.msp-today{position:absolute;top:0;bottom:0;width:2px;background:#c81e1e;z-index:5;pointer-events:none}
.msp-today::before{content:'';position:absolute;top:-1px;left:50%;transform:translateX(-50%);color:#c81e1e;font-size:9px;line-height:1}
/* Дата-шапка на сетке */
.msp-date-hdr{height:28px;position:relative;border-bottom:1px solid #c8c8d8;background:#e8e8f0}
.msp-date-tick{position:absolute;top:0;bottom:0;display:flex;align-items:center;font-size:10px;font-weight:700;color:#555;padding-left:4px;border-left:1px solid #c8c8d8;white-space:nowrap}
/* ── ELENA INTAKE v2 ── */
.intake-v2{margin:0 0 80px}
.intent-chips{display:flex;gap:10px;margin:4px 0 4px 51px;flex-wrap:wrap}
.intent-chip{background:var(--card);border:1.5px solid var(--line);border-radius:13px;padding:12px 16px;cursor:pointer;font-size:13px;font-weight:700;flex:1;min-width:160px;transition:border-color .15s,transform .1s}
.intent-chip:hover{border-color:var(--bg);transform:translateY(-1px)}
.intent-chip:active{transform:translateY(0)}
.intent-chip .ic-ico{font-size:20px;margin-bottom:4px}
.intent-chip .ic-lbl{font-weight:700;color:var(--ink)}
.intent-chip .ic-sub{font-size:11px;color:var(--mut);font-weight:400;margin-top:2px}
.voice-input-row{display:flex;gap:8px;margin:12px 0 12px 51px;align-items:center}
.voice-input-row input{flex:1;border:1.5px solid var(--line);border-radius:11px;padding:11px 14px;font-size:14px;font-family:inherit;outline:none;background:#fff;color:var(--ink)}
.voice-input-row input:focus{border-color:var(--bg)}
.voice-btn{width:44px;height:44px;border-radius:50%;border:1.5px solid var(--line);background:#fff;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:all .15s;flex-shrink:0}
.voice-btn:hover{border-color:var(--bg);background:var(--tint)}
.voice-btn.recording{background:#fee2e2;border-color:#ef4444;animation:voicePulse .8s ease infinite}
@keyframes voicePulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,.4)}50%{box-shadow:0 0 0 8px rgba(239,68,68,0)}}
.voice-btn.no-support{display:none}
.send-btn{width:44px;height:44px;border-radius:11px;border:none;background:var(--bg);color:#fff;cursor:pointer;font-size:16px;font-weight:700;flex-shrink:0}
.send-btn:hover{background:var(--bghv)}
.voice-hint{font-size:11px;color:var(--mut);margin:0 0 8px 51px}
/* Шаг составления */
.create-step{margin:8px 0 80px 0}
.create-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:4px 0 16px 51px}
.create-type-card{background:var(--card);border:1.5px solid var(--line);border-radius:13px;padding:14px;cursor:pointer;transition:border-color .15s}
.create-type-card:hover{border-color:var(--bg)}
.create-type-card .ctc-ico{font-size:24px;margin-bottom:6px}
.create-type-card .ctc-name{font-weight:700;font-size:13px}
.create-type-card .ctc-sub{font-size:11px;color:var(--mut);margin-top:2px}
/* ── КАРТОЧКА ДЕЛА CRM ── */
.case-hdr{background:#fff;border:1px solid #e5e7eb;border-radius:14px;padding:20px 22px;margin-bottom:20px;display:flex;align-items:flex-start;gap:18px}
.case-hdr-ico{width:52px;height:52px;border-radius:13px;background:var(--tint);display:flex;align-items:center;justify-content:center;font-size:26px;flex-shrink:0}
.case-hdr-info{flex:1;min-width:0}
.case-hdr-name{font-size:18px;font-weight:800;margin-bottom:6px;line-height:1.2}
.case-hdr-meta{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.case-hdr-actions{display:flex;gap:8px;flex-shrink:0;align-items:flex-start}
.next-step-v2{background:linear-gradient(135deg,#fff7f8,#fff);border:2px solid var(--bg);border-radius:16px;padding:20px 22px;margin-bottom:22px;display:flex;align-items:center;gap:16px;cursor:pointer;transition:box-shadow .15s}
.next-step-v2:hover{box-shadow:0 4px 20px rgba(159,18,57,.12)}
.ns2-pulse{width:48px;height:48px;border-radius:13px;background:var(--bg);display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;animation:nsPulse 2s ease-in-out infinite}
@keyframes nsPulse{0%,100%{box-shadow:0 0 0 0 rgba(159,18,57,.3)}50%{box-shadow:0 0 0 8px rgba(159,18,57,0)}}
.ns2-body{flex:1;min-width:0}
.ns2-label{font-size:11px;font-weight:700;color:var(--bg);text-transform:uppercase;letter-spacing:.6px;margin-bottom:4px}
.ns2-action{font-size:16px;font-weight:800;color:var(--ink);margin-bottom:4px}
.ns2-meta{font-size:12px;color:var(--mut)}
.ns2-deadline{background:#fee2e2;color:#991b1b;border-radius:7px;padding:2px 9px;font-size:11px;font-weight:700;margin-left:8px}
.ns2-btn{background:var(--bg);color:#fff;border:none;border-radius:10px;padding:10px 18px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;flex-shrink:0;white-space:nowrap}
.ns2-btn:hover{background:var(--bghv)}
.case-tabs{display:flex;gap:0;border-bottom:2px solid #e5e7eb;margin-bottom:20px}
.case-tab{padding:10px 18px;font-size:13px;font-weight:600;color:#6b7280;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-2px;transition:all .15s}
.case-tab:hover{color:var(--ink)}
.case-tab.on{color:var(--bg);border-bottom-color:var(--bg);font-weight:700}
.case-pane{display:none}.case-pane.on{display:block}
.overview-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px}
.ov-card{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:16px 18px}
.ov-card-ttl{font-size:11px;font-weight:700;color:var(--mut);text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px}
.ov-row{display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px solid #f3f4f6;font-size:13px}
.ov-row:last-child{border-bottom:none}
.ov-row .ov-k{color:var(--mut)}
.ov-row .ov-v{font-weight:600}
/* ── ELENA INTENT v3 ── */
.elena-q{font-size:15px;font-weight:700;color:var(--ink);margin:4px 0 14px 51px;line-height:1.4}
.intent-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:0 0 12px 51px;max-width:580px}
.int-card{background:#fff;border:2px solid #e5e7eb;border-radius:14px;padding:16px 14px;cursor:pointer;transition:all .15s;display:flex;align-items:flex-start;gap:12px}
.int-card:hover{border-color:var(--bg);background:var(--tint);transform:translateY(-1px);box-shadow:0 4px 14px rgba(159,18,57,.08)}
.int-card:active{transform:none}
.int-ico{font-size:24px;flex-shrink:0;line-height:1}
.int-body{}
.int-lbl{font-size:13px;font-weight:700;color:var(--ink);margin-bottom:2px}
.int-sub{font-size:11px;color:var(--mut);line-height:1.4}
.elena-voice-row{display:flex;align-items:center;gap:10px;margin:0 0 4px 51px}
.elena-voice-input{flex:1;border:1.5px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;font-family:inherit;outline:none;max-width:440px}
.elena-voice-input:focus{border-color:var(--bg)}
.voice-btn-sm{background:var(--bg);color:#fff;border:none;border-radius:10px;padding:10px 14px;font-size:16px;cursor:pointer;flex-shrink:0;transition:background .15s}
.voice-btn-sm:hover{background:var(--bghv)}
.voice-btn-sm.pulse{animation:vPulse 1s ease-in-out infinite}
@keyframes vPulse{0%,100%{box-shadow:0 0 0 0 rgba(159,18,57,.4)}50%{box-shadow:0 0 0 8px rgba(159,18,57,0)}}
/* ── ELENA CHAT ENTRY v4 ── */
.elena-anim{opacity:0;transform:translateY(8px);animation:elFadeIn .38s ease forwards;animation-delay:var(--d,0s)}
@keyframes elFadeIn{to{opacity:1;transform:translateY(0)}}
.elena-dialog-wrap{margin:18px 0 10px 51px;max-width:560px}
.elena-dialog-row{display:flex;gap:8px;align-items:center;background:#fff;border:2.5px solid var(--bg);border-radius:14px;padding:10px 14px;box-shadow:0 0 0 5px rgba(159,18,57,.07)}
.elena-main-inp{flex:1;border:none;outline:none;font-size:14px;font-family:inherit;color:var(--ink);background:transparent;padding:6px 0}
.elena-main-inp::placeholder{color:#b0b7c3}
.elena-go-btn{background:var(--bg);color:#fff;border:none;border-radius:10px;padding:9px 20px;font-size:16px;font-weight:700;cursor:pointer;flex-shrink:0;transition:background .15s}
.elena-go-btn:hover{background:var(--bghv)}
.elena-quick{margin-top:4px}
.elena-q-lbl{font-size:11px;font-weight:700;color:var(--mut);margin:14px 0 10px 51px;letter-spacing:.04em}
/* Стартовый экран — усиление Елены */
.hero-elena-hint{background:rgba(255,255,255,.08);border-radius:12px;padding:12px 14px;margin:14px 0;font-size:12px;color:rgba(255,255,255,.7);line-height:1.5}
.hero-elena-hint b{color:#fff}
.hero-services{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0}
.hero-svc{background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.2);border-radius:20px;padding:5px 13px;font-size:11px;font-weight:600;color:rgba(255,255,255,.85);white-space:nowrap}
/* ── СОСТАВИТЬ ДОКУМЕНТ ── */
.create-steps{display:flex;align-items:center;gap:0;margin-bottom:28px}
.cstep{display:flex;align-items:center;gap:8px;padding:9px 16px;border-radius:10px;font-size:13px;font-weight:600;color:var(--mut);border:1.5px solid #e5e7eb;background:#fff;transition:all .2s}
.cstep.act{background:var(--bg);color:#fff;border-color:var(--bg)}
.cstep.done{background:var(--tint);color:var(--bg);border-color:var(--bg)}
.cstep-num{width:20px;height:20px;border-radius:50%;background:rgba(0,0,0,.08);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800}
.cstep.act .cstep-num{background:rgba(255,255,255,.25)}
.cstep.done .cstep-num{background:var(--bg);color:#fff}
.cstep-arrow{color:#d1d5db;font-size:18px;margin:0 6px;flex-shrink:0}
.create-pane{display:none}.create-pane.on{display:block}
/* Тип документа */
.doc-type-intro{font-size:14px;color:var(--mut);margin-bottom:20px;line-height:1.5}
.doc-type-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;max-width:800px;margin-bottom:24px}
.doc-type-card{background:#fff;border:2px solid #e5e7eb;border-radius:14px;padding:18px 14px;cursor:pointer;transition:all .15s;text-align:center}
.doc-type-card:hover{border-color:var(--bg);background:var(--tint);transform:translateY(-2px);box-shadow:0 4px 14px rgba(159,18,57,.1)}
.doc-type-card.sel{border-color:var(--bg);background:var(--tint);box-shadow:0 2px 10px rgba(159,18,57,.15)}
.dtc-ico{font-size:30px;margin-bottom:8px}
.dtc-name{font-size:12px;font-weight:700;color:var(--ink);line-height:1.3}
.dtc-desc{font-size:10px;color:var(--mut);margin-top:3px}
.doc-type-next{background:var(--bg);color:#fff;border:none;border-radius:11px;padding:12px 28px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s;display:none}
.doc-type-next.show{display:inline-flex;align-items:center;gap:8px}
.doc-type-next:hover{background:var(--bghv)}
/* Форма параметров */
.create-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:740px;margin-bottom:20px}
.cf-group{display:flex;flex-direction:column;gap:5px}
.cf-group.wide{grid-column:1/-1}
.cf-label{font-size:11px;font-weight:700;color:var(--mut);text-transform:uppercase;letter-spacing:.4px}
.cf-input{border:1.5px solid #e5e7eb;border-radius:9px;padding:10px 13px;font-size:13px;font-family:inherit;color:var(--ink);outline:none;transition:border-color .15s;background:#fff}
.cf-input:focus{border-color:var(--bg)}
.cf-select{border:1.5px solid #e5e7eb;border-radius:9px;padding:10px 13px;font-size:13px;font-family:inherit;color:var(--ink);outline:none;background:#fff;cursor:pointer}
.cf-hint{font-size:11px;color:var(--mut);margin-top:2px;line-height:1.4}
.create-form-actions{display:flex;gap:10px;margin-top:8px;max-width:740px}
.cf-back{background:#fff;color:var(--bg);border:1.5px solid var(--bg);border-radius:11px;padding:11px 22px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}
.cf-generate{background:var(--bg);color:#fff;border:none;border-radius:11px;padding:11px 26px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;display:flex;align-items:center;gap:8px}
.cf-generate:hover{background:var(--bghv)}
/* Превью документа */
.doc-generating{text-align:center;padding:40px 20px;display:none}
.doc-generating.show{display:block}
.dg-spinner{width:48px;height:48px;border:4px solid var(--tint);border-top-color:var(--bg);border-radius:50%;animation:spin .8s linear infinite;margin:0 auto 16px}
@keyframes spin{to{transform:rotate(360deg)}}
.dg-text{font-size:15px;font-weight:700;color:var(--ink);margin-bottom:6px}
.dg-sub{font-size:13px;color:var(--mut)}
.doc-preview-wrap{display:none;max-width:700px}
.doc-preview-wrap.show{display:block}
.doc-preview-actions{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap}
.dpa-btn{display:flex;align-items:center;gap:7px;border-radius:10px;padding:10px 18px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;border:none;transition:all .15s}
.dpa-dl{background:var(--bg);color:#fff}
.dpa-dl:hover{background:var(--bghv)}
.dpa-copy{background:#fff;color:var(--bg);border:1.5px solid var(--bg)}
.dpa-add{background:#f0fdf4;color:#166534;border:1.5px solid #86efac}
.doc-preview{background:#fff;border:1px solid #e5e7eb;border-radius:14px;padding:32px 36px;font-size:13px;line-height:1.8;color:#374151;font-family:Georgia,serif}
.doc-preview h3{font-family:var(--font-ui);font-size:15px;font-weight:800;text-align:center;margin-bottom:6px;color:var(--ink)}
.doc-preview .doc-city{text-align:right;font-size:12px;color:var(--mut);margin-bottom:20px}
.doc-preview .doc-p{margin-bottom:10px}
.doc-preview .doc-article{font-weight:700;margin-top:16px;margin-bottom:6px;font-family:var(--font-ui);font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--bg)}
.doc-preview .doc-clause{margin-bottom:6px;padding-left:12px;border-left:2px solid #e5e7eb}
.doc-preview .doc-highlight{background:#fef9c3;border-radius:3px;padding:1px 3px}
.doc-preview .doc-blank{background:#e5e7eb;border-radius:3px;padding:1px 8px;color:var(--mut);font-style:italic}
.doc-preview .doc-parties{background:#f8f9fa;border-radius:10px;padding:14px 16px;margin-bottom:16px;font-size:12px}
.doc-preview .doc-parties b{display:block;margin-bottom:4px;font-family:var(--font-ui)}
/* ── ЧАСЫ В САЙДБАРЕ ── */
.side-clock{padding:10px 16px 6px;border-top:1px solid rgba(255,255,255,.07);margin-top:auto}
.side-clock-day{font-size:10px;font-weight:600;color:rgba(255,255,255,.45);text-transform:uppercase;letter-spacing:.5px;margin-bottom:2px}
.side-clock-date{font-size:13px;font-weight:700;color:rgba(255,255,255,.85);margin-bottom:2px}
.side-clock-time{font-size:18px;font-weight:800;color:#fff;letter-spacing:.5px;font-variant-numeric:tabular-nums}
/* ── ПАНЕЛЬ ПРОТОКОЛА ── */
.protocol-bar{display:none;align-items:center;gap:14px;background:linear-gradient(135deg,#fff7f8,#fff);border:2px solid var(--bg);border-radius:14px;padding:14px 18px;margin-bottom:18px;animation:slideIn .25s ease}
.protocol-bar.show{display:flex}
@keyframes slideIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.pb-ico{font-size:22px;flex-shrink:0}
.pb-body{flex:1;min-width:0}
.pb-title{font-size:13px;font-weight:800;color:var(--ink)}
.pb-sub{font-size:11px;color:var(--mut);margin-top:1px}
.pb-btn{background:var(--bg);color:#fff;border:none;border-radius:10px;padding:10px 20px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;flex-shrink:0}
.pb-btn:hover{background:var(--bghv)}
/* ── ПЕРЕПИСКА ── */
.chat-compose{background:#fff;border:1.5px solid var(--line);border-radius:14px;padding:18px 20px;margin-bottom:18px;max-width:700px}
.chat-compose-ttl{font-size:13px;font-weight:700;margin-bottom:12px;color:var(--ink)}
.chat-field{margin-bottom:10px}
.chat-field label{font-size:11px;font-weight:600;color:var(--mut);text-transform:uppercase;letter-spacing:.4px;display:block;margin-bottom:4px}
.chat-field input,.chat-field textarea{width:100%;border:1px solid var(--line);border-radius:8px;padding:9px 12px;font-size:13px;font-family:inherit;color:var(--ink);resize:vertical;outline:none;transition:border-color .15s}
.chat-field input:focus,.chat-field textarea:focus{border-color:var(--bg)}
.chat-actions{display:flex;gap:8px;margin-top:4px}
.chat-send-btn{background:var(--bg);color:#fff;border:none;border-radius:9px;padding:9px 20px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}
.chat-send-btn:hover{background:var(--bghv)}
.chat-copy-btn{background:#fff;color:var(--bg);border:1.5px solid var(--bg);border-radius:9px;padding:9px 16px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}
.chat-list{max-width:700px}
.chat-list-ttl{font-size:11px;font-weight:700;color:var(--mut);text-transform:uppercase;letter-spacing:.4px;margin-bottom:10px}
.chat-item{display:flex;gap:12px;align-items:flex-start;padding:12px 14px;background:#fff;border:1px solid var(--line);border-radius:12px;margin-bottom:8px;cursor:pointer;transition:box-shadow .12s}
.chat-item:hover{box-shadow:0 2px 10px rgba(0,0,0,.07)}
.chat-item.out .chat-arrow{color:var(--bg);font-size:16px;flex-shrink:0;margin-top:2px}
.chat-item.inc .chat-arrow{color:#16a34a;font-size:16px;flex-shrink:0;margin-top:2px}
.chat-item-body{flex:1;min-width:0}
.chat-item-hdr{display:flex;justify-content:space-between;align-items:center;margin-bottom:3px}
.chat-item-subj{font-size:13px;font-weight:700;color:var(--ink)}
.chat-item-date{font-size:11px;color:var(--mut);flex-shrink:0;margin-left:8px}
.chat-item-prev{font-size:12px;color:var(--mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.chat-status{font-size:10px;font-weight:700;padding:2px 7px;border-radius:6px;flex-shrink:0}
.chat-status.sent{background:#dbeafe;color:#1d4ed8}
.chat-status.read{background:#dcfce7;color:#166534}
.chat-status.new{background:#fee2e2;color:#991b1b}
/* ── РИСКИ АККОРДЕОН ── */
.risk-item{cursor:pointer}
.risk-toggle{margin-left:auto;color:var(--mut);font-size:11px;transition:transform .2s;flex-shrink:0;padding-left:8px}
.risk-item.expanded .risk-toggle{transform:rotate(180deg)}
.risk-expand{display:none;margin-top:12px;border-top:1px solid #f3f4f6;padding-top:12px}
.risk-item.expanded .risk-expand{display:block}
.risk-quote{background:#fff5f5;border:1px solid #fecaca;border-radius:8px;padding:10px 13px;margin-bottom:8px}
.risk-quote-lbl{font-size:10px;font-weight:700;color:#991b1b;text-transform:uppercase;letter-spacing:.4px;margin-bottom:5px}
.risk-quote-txt{font-size:12px;color:#374151;font-style:italic;line-height:1.6}
.risk-fix{background:#f0fdf4;border:1px solid #86efac;border-radius:8px;padding:10px 13px;margin-bottom:10px}
.risk-fix-lbl{font-size:10px;font-weight:700;color:#166534;text-transform:uppercase;letter-spacing:.4px;margin-bottom:5px}
.risk-fix-txt{font-size:12px;color:#374151;line-height:1.6}
.risk-apply-btn{background:var(--bg);color:#fff;border:none;border-radius:8px;padding:8px 18px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s}
.risk-apply-btn:hover{background:var(--bghv)}
/* ── КАРТОЧКА ДЕЛА v2 ── */
/* Прогресс-шаги */
.case-progress{display:flex;align-items:flex-start;gap:0;margin-bottom:20px;overflow-x:auto;padding-bottom:4px}
.cp-step{display:flex;flex-direction:column;align-items:center;flex:1;min-width:90px;position:relative}
.cp-step:not(:last-child)::after{content:'';position:absolute;top:14px;left:calc(50% + 16px);right:calc(-50% + 16px);height:2px;background:#e5e7eb;z-index:0}
.cp-step.done .cp-step:not(:last-child)::after,.cp-step.act::after,.cp-step.done::after{background:var(--bg)}
.cps-dot{width:28px;height:28px;border-radius:50%;border:2px solid #e5e7eb;background:#fff;display:flex;align-items:center;justify-content:center;font-size:13px;z-index:1;position:relative;flex-shrink:0}
.cp-step.done .cps-dot{background:var(--bg);border-color:var(--bg);color:#fff}
.cp-step.act .cps-dot{background:#fff;border-color:var(--bg);box-shadow:0 0 0 3px rgba(159,18,57,.15)}
.cp-step.pend .cps-dot{background:#f9fafb;border-color:#d1d5db;color:#9ca3af}
.cps-lbl{font-size:10px;font-weight:600;margin-top:6px;text-align:center;color:var(--mut);max-width:80px;line-height:1.3}
.cp-step.done .cps-lbl{color:var(--bg)}
.cp-step.act .cps-lbl{color:var(--ink);font-weight:700}
.cps-date{font-size:9px;color:var(--mut);margin-top:2px;text-align:center}
.cp-step.act .cps-date{color:var(--bg)}
/* Риски */
.risk-summary{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap}
.risk-sum-item{display:flex;align-items:center;gap:6px;background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:7px 14px;font-size:13px;font-weight:700}
.risk-sum-item.crit{border-color:#fecaca;background:#fff5f5;color:#991b1b}
.risk-sum-item.mid{border-color:#fde68a;background:#fffbeb;color:#92400e}
.risk-sum-item.low{border-color:#bbf7d0;background:#f0fdf4;color:#166534}
.risk-list{display:flex;flex-direction:column;gap:8px;max-width:760px}
.risk-item{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:14px 16px;cursor:pointer;transition:box-shadow .12s}
.risk-item:hover{box-shadow:0 2px 12px rgba(0,0,0,.07)}
.risk-item.crit{border-left:4px solid #ef4444}
.risk-item.mid{border-left:4px solid #f59e0b}
.risk-item.low{border-left:4px solid #22c55e}
.risk-item-hdr{display:flex;align-items:center;gap:10px;margin-bottom:6px}
.risk-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:6px;flex-shrink:0;white-space:nowrap}
.risk-badge.crit{background:#fee2e2;color:#991b1b}
.risk-badge.mid{background:#fef3c7;color:#92400e}
.risk-badge.low{background:#dcfce7;color:#166534}
.risk-num{font-size:11px;color:var(--mut);flex-shrink:0}
.risk-title{font-size:13px;font-weight:700;color:var(--ink);flex:1}
.risk-norm{font-size:11px;color:var(--mut);margin-bottom:6px}
.risk-rec{font-size:12px;color:var(--ink);line-height:1.5;background:#f8f9fa;border-radius:7px;padding:7px 10px}
/* Документы: upload zone */
.doc-upload-btn{display:flex;align-items:center;gap:10px;border:2px dashed #d1d5db;border-radius:12px;padding:14px 18px;margin-bottom:14px;cursor:pointer;transition:border-color .15s;max-width:700px;background:#fafafa}
.doc-upload-btn:hover{border-color:var(--bg);background:#fff7f8}
.doc-upload-ico{font-size:22px}
.doc-upload-txt{font-size:13px;font-weight:600;color:var(--mut)}
.doc-upload-txt span{display:block;font-size:11px;font-weight:400;margin-top:1px}
/* ── RETURNING CLIENT ── */
.ret-greet{font-size:30px;font-weight:800;line-height:1.2;margin-bottom:24px;letter-spacing:-.5px}
.ret-sub{font-size:16px;color:rgba(255,255,255,.7);margin-bottom:22px}
.ret-card{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.2);border-radius:14px;padding:16px 18px;margin-bottom:22px}
.ret-card:empty{display:none}
/* ── HERO TYPEWRITER ── */
.hero-tw-wrap{margin:14px 0 4px;display:flex;align-items:center;gap:7px;min-height:28px}
/* ── HERO CHAT ── */
.hero-chat{background:rgba(255,255,255,.97);border-radius:18px;overflow:hidden;box-shadow:0 8px 40px rgba(0,0,0,.22);margin:14px 0 0;backdrop-filter:blur(12px)}
.hero-chat-hdr{display:flex;align-items:center;gap:10px;padding:10px 14px;background:rgba(159,18,57,.07);border-bottom:1px solid rgba(159,18,57,.12)}
.hero-chat-av{width:34px;height:34px;border-radius:50%;object-fit:cover;flex-shrink:0}
.hero-chat-name{font-size:13px;font-weight:800;color:#1f2937;line-height:1.2}
.hero-chat-status{display:flex;align-items:center;gap:4px;font-size:11px;color:#6b7280}
.hero-chat-dot{width:7px;height:7px;border-radius:50%;background:var(--bg);display:inline-block;animation:hcPulse 2s ease infinite}
@keyframes hcPulse{0%,100%{opacity:1}50%{opacity:.5}}
.hero-chat-msgs{padding:12px 12px 8px;display:flex;flex-direction:column;gap:8px;min-height:100px;max-height:240px;overflow-y:auto}
.hc-msg{display:flex;align-items:flex-end;gap:6px;animation:hcIn .3s ease forwards;opacity:0}
@keyframes hcIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
.hc-av{width:24px;height:24px;border-radius:50%;object-fit:cover;flex-shrink:0}
.hc-bubble{background:#f3f4f6;border-radius:16px 16px 16px 4px;padding:9px 13px;font-size:13px;color:#1f2937;line-height:1.5;max-width:85%}
.hc-bubble b{color:#9f1239}
.hc-bubble.user{background:#9f1239;color:#fff;border-radius:16px 16px 4px 16px;margin-left:auto}
.hc-user-msg{justify-content:flex-end}
.hc-user-msg .hc-bubble{background:#9f1239;color:#fff;border-radius:16px 16px 4px 16px}
.hc-typing{display:flex;align-items:flex-end;gap:6px}
.hc-typing-dots{background:#f3f4f6;border-radius:16px 16px 16px 4px;padding:10px 14px;display:flex;gap:4px;align-items:center}
.hc-typing-dots span{width:7px;height:7px;border-radius:50%;background:#9ca3af;animation:hcDot 1.2s ease infinite}
.hc-typing-dots span:nth-child(2){animation-delay:.2s}
.hc-typing-dots span:nth-child(3){animation-delay:.4s}
@keyframes hcDot{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-5px)}}
.hero-chat-replies{display:flex;flex-wrap:wrap;gap:6px;padding:8px 12px;border-top:1px solid #f3f4f6}
.hc-reply{padding:6px 12px;border:1.5px solid #e5e7eb;border-radius:20px;font-size:12px;font-weight:600;color:#374151;background:#fff;cursor:pointer;transition:all .15s;white-space:nowrap}
.hc-reply:hover{border-color:#9f1239;color:#9f1239;background:#fff5f5}
.hero-chat-input-row{display:flex;gap:6px;padding:8px 10px;border-top:1px solid #f3f4f6;background:#fafafa}
.hc-text-inp{flex:1;border:1.5px solid #e5e7eb;border-radius:20px;padding:7px 14px;font-size:13px;outline:none;background:#fff;font-family:inherit}
.hc-text-inp:focus{border-color:#9f1239}
.hc-send-btn{background:#9f1239;color:#fff;border:none;border-radius:50%;width:34px;height:34px;font-size:16px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:background .15s}
.hc-send-btn:hover{background:#7f1d2e}
.hc-mic-btn{background:transparent;border:1.5px solid #e5e7eb;color:#6b7280;border-radius:50%;width:34px;height:34px;font-size:15px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:all .15s;padding:0}
.hc-mic-btn:hover{border-color:#9f1239;color:#9f1239}
.hc-mic-btn.recording{background:#fee2e2;border-color:#9f1239;color:#9f1239;animation:micPulse .7s ease-in-out infinite}
@keyframes micPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.12)}}
.svc-order-card{margin:10px 0 14px 48px;border:1.5px solid #e5e7eb;border-radius:14px;padding:16px;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.06)}
.svc-ico{font-size:26px;margin-bottom:8px}
.svc-name{font-size:15px;font-weight:700;color:#1f2937;margin-bottom:4px}
.svc-desc{font-size:12px;color:#6b7280;margin-bottom:8px;line-height:1.45}
.svc-price{font-size:18px;font-weight:700;color:#9f1239}
.svc-time{font-size:12px;font-weight:400;color:#6b7280}
.svc-actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap;align-items:center}
.svc-btn-detail{padding:7px 16px;font-size:13px;background:transparent;border:1.5px solid #9f1239;color:#9f1239;border-radius:20px;cursor:pointer;transition:all .15s;font-family:inherit}
.svc-btn-detail:hover{background:#fff5f7}
.svc-detail-inp-row{display:flex;gap:8px;margin-top:10px;align-items:center}
.doc-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:10px 0 16px 48px}
.doc-type-card{border:1.5px solid #e5e7eb;border-radius:12px;padding:14px;cursor:pointer;background:#fff;transition:all .15s}
.doc-type-card:hover{border-color:#9f1239;background:#fff5f7;transform:translateY(-1px)}
.dt-ico{font-size:22px;margin-bottom:6px}
.dt-lbl{font-size:14px;font-weight:600;color:#1f2937;margin-bottom:3px}
.dt-sub{font-size:11px;color:#6b7280;line-height:1.3}
.hero-tw-cur{color:rgba(255,255,255,.5);font-size:18px;flex-shrink:0;line-height:1;animation:twBlink .65s step-start infinite}
@keyframes twBlink{0%,100%{opacity:1}50%{opacity:0}}
.hero-tw-text{font-size:14px;font-weight:600;color:rgba(255,255,255,.82);line-height:1.4}
.ret-tw-wrap{margin:0 0 22px;display:flex;align-items:baseline;gap:7px;min-height:32px}
/* ── RETURNING CHAT ── */
.ret-chat{background:rgba(255,255,255,.97);border-radius:18px;overflow:hidden;box-shadow:0 8px 40px rgba(0,0,0,.22);margin:0 0 14px}
.ret-chat-stat-row{display:flex;gap:8px;margin-bottom:4px;flex-wrap:wrap}
.ret-stat{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);border-radius:10px;padding:5px 12px;font-size:12px;font-weight:700;color:rgba(255,255,255,.95)}
.ret-stat.warn{background:rgba(239,68,68,.25);border-color:rgba(239,68,68,.4)}
.ret-stat.green{background:rgba(34,197,94,.2);border-color:rgba(34,197,94,.35)}
.ret-tw-wrap .hero-tw-cur{font-size:20px;color:rgba(255,255,255,.45)}
.ret-tw-wrap .hero-tw-text{font-size:18px;font-weight:700;color:rgba(255,255,255,.95)}
.ret-ord-lbl{font-size:11px;font-weight:700;color:rgba(255,255,255,.45);text-transform:uppercase;letter-spacing:.6px;margin-bottom:7px}
.ret-ord-name{font-size:15px;font-weight:700;margin-bottom:3px}
.ret-ord-price{font-size:22px;font-weight:800;color:#ffaaaa}
/* ── ЮKASSA ВИДЖЕТ ── */
.yk-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:99990;display:none;align-items:flex-end;justify-content:center}
.yk-overlay.open{display:flex}
@media(min-width:520px){.yk-overlay{align-items:center}}
.yk-sheet{background:#fff;border-radius:20px 20px 0 0;width:100%;max-width:420px;padding:24px 20px 32px;position:relative;box-shadow:0 -8px 40px rgba(0,0,0,.18);animation:ykUp .25s ease}
@media(min-width:520px){.yk-sheet{border-radius:20px}}
@keyframes ykUp{from{transform:translateY(60px);opacity:0}to{transform:none;opacity:1}}
.yk-close{position:absolute;top:14px;right:16px;background:none;border:none;font-size:20px;cursor:pointer;color:#9ca3af;line-height:1}
.yk-header{display:flex;align-items:center;gap:10px;margin-bottom:20px}
.yk-logo{font-size:13px;font-weight:800;color:#0057FF;letter-spacing:-.5px}
.yk-logo span{color:#FF3D00}
.yk-amount{margin-left:auto;font-size:18px;font-weight:800;color:#111}
.yk-field{margin-bottom:14px}
.yk-field label{display:block;font-size:12px;color:#6b7280;font-weight:600;margin-bottom:5px;font-family:inherit}
.yk-field input{width:100%;border:1.5px solid #e5e7eb;border-radius:10px;padding:11px 14px;font-size:15px;font-family:inherit;outline:none;color:#111;background:#fff;box-sizing:border-box;transition:border-color .15s}
.yk-field input:focus{border-color:#0057FF}
.yk-field input.yk-error{border-color:#ef4444}
.yk-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.yk-sbp{display:flex;align-items:center;justify-content:center;gap:8px;border:1.5px solid #e5e7eb;border-radius:10px;padding:11px 14px;cursor:pointer;font-size:14px;font-weight:600;color:#111;margin-bottom:14px;transition:border-color .15s;font-family:inherit;background:#fff;width:100%;box-sizing:border-box}
.yk-sbp:hover{border-color:#0057FF;color:#0057FF}
.yk-sbp-icon{width:24px;height:14px;background:linear-gradient(90deg,#1D63D8 33%,#fff 33% 66%,#ED1C24 66%);border-radius:3px;font-size:9px;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:900;letter-spacing:-.5px}
.yk-sep{display:flex;align-items:center;gap:8px;color:#9ca3af;font-size:12px;margin:12px 0}
.yk-sep::before,.yk-sep::after{content:'';flex:1;height:1px;background:#e5e7eb}
.yk-pay-btn{width:100%;background:#0057FF;color:#fff;border:none;border-radius:12px;padding:15px;font-size:16px;font-weight:800;cursor:pointer;font-family:inherit;margin-top:4px;transition:background .15s}
.yk-pay-btn:hover{background:#0046CC}
.yk-pay-btn:disabled{background:#9ca3af;cursor:default}
.yk-footer{text-align:center;font-size:11px;color:#9ca3af;margin-top:12px}
.yk-footer a{color:#9ca3af}
.yk-spinner{display:none;width:20px;height:20px;border:3px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:ykSpin .7s linear infinite;margin:0 auto}
@keyframes ykSpin{to{transform:rotate(360deg)}}
.yk-success{display:none;text-align:center;padding:20px 0 8px}
.yk-success .yk-check{font-size:48px;margin-bottom:12px}
.yk-success .yk-sttl{font-size:18px;font-weight:800;color:#111;margin-bottom:6px}
.yk-success .yk-ssub{font-size:13px;color:#6b7280}
/* ── ЭКРАН СТАТУСА ЗАКАЗА ── */
.os-wrap{max-width:560px;margin:0 auto;padding:24px 20px 60px}
.os-ok{text-align:center;margin:8px 0 20px}
.os-ok-icon{font-size:44px;margin-bottom:8px}
.os-ok-ttl{font-size:20px;font-weight:800;color:var(--bg)}
.os-ok-sub{font-size:13px;color:var(--mut);margin-top:4px}
.os-card{background:var(--card);border:1.5px solid var(--line);border-radius:14px;padding:16px 18px;margin-bottom:20px}
.os-card .oc-ttl{font-size:16px;font-weight:800;margin-bottom:4px}
.os-card .oc-plan{font-size:13px;color:var(--mut);margin-bottom:10px}
.os-card .oc-price{font-size:22px;font-weight:800;color:var(--bg)}
.os-card .oc-id{font-size:11px;color:var(--mut);margin-top:4px;letter-spacing:.3px}
.os-steps{display:flex;align-items:flex-start;gap:0;margin:0 0 20px;position:relative}
.os-steps::before{content:'';position:absolute;top:13px;left:13px;right:13px;height:2px;background:var(--line);z-index:0}
.os-step{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative;z-index:1}
.os-dot{width:26px;height:26px;border-radius:50%;border:2.5px solid var(--line);background:var(--surf);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:var(--mut);flex-shrink:0}
.os-step.done .os-dot{background:var(--ok);border-color:var(--ok);color:#fff;font-size:13px}
.os-step.active .os-dot{background:var(--bg);border-color:var(--bg);color:#fff;font-size:13px}
.os-step-lbl{font-size:11px;color:var(--mut);text-align:center;line-height:1.35;max-width:80px}
.os-step.done .os-step-lbl{color:var(--ok);font-weight:600}
.os-step.active .os-step-lbl{color:var(--bg);font-weight:700}
.os-deadline{background:var(--tint);border:1.5px solid rgba(159,18,57,.2);border-radius:10px;padding:10px 14px;font-size:13px;margin:0 0 16px;display:flex;align-items:center;gap:8px}
.os-deadline .od-icon{font-size:16px}
.os-deadline .od-text strong{display:block;font-weight:700;color:var(--bg);font-size:13px}
.os-deadline .od-text span{font-size:12px;color:var(--mut)}
.os-actions{display:flex;gap:10px;flex-wrap:wrap}
.os-tg{display:flex;align-items:center;gap:8px;background:var(--card);border:1.5px solid var(--line);border-radius:10px;padding:10px 16px;font-size:13px;font-weight:700;cursor:pointer;color:var(--ink);text-decoration:none;font-family:inherit}
.os-tg:hover{border-color:var(--bg);color:var(--bg)}
/* ── АНАЛИТИКА CUSTOM-ЗАПРОСОВ ── */
#custom-admin{background:var(--surf)}
.ca-wrap{max-width:780px;margin:0 auto;padding:28px 20px 60px}
.ca-title{font-size:20px;font-weight:800;margin-bottom:4px}
.ca-sub{font-size:13px;color:var(--mut);margin-bottom:24px}
.ca-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:24px}
.ca-stat{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:14px 16px}
.ca-stat .sv{font-size:26px;font-weight:800;color:var(--bg)}
.ca-stat .sl{font-size:11px;color:var(--mut);margin-top:2px;text-transform:uppercase;letter-spacing:.5px}
.ca-section{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--mut);margin:20px 0 10px}
.ca-words{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:24px}
.ca-word{background:var(--card);border:1.5px solid var(--line);border-radius:20px;padding:5px 12px;font-size:13px;font-weight:600;display:flex;align-items:center;gap:6px;cursor:default}
.ca-word .wc{background:var(--bg);color:#fff;border-radius:10px;font-size:10px;padding:1px 6px;font-weight:700}
.ca-word.w-top{border-color:rgba(159,18,57,.4);background:var(--tint)}
.ca-card{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:14px 16px;margin-bottom:10px}
.ca-card.ca-done{opacity:.5;border-style:dashed}
.ca-card .cc-head{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.ca-card .cc-ctype{font-size:11px;font-weight:700;background:var(--tint);color:var(--bg);padding:2px 8px;border-radius:8px}
.ca-card .cc-ts{font-size:11px;color:var(--mut);margin-left:auto}
.ca-card .cc-text{font-size:13.5px;line-height:1.6;color:var(--ink);margin-bottom:10px}
.ca-card .cc-actions{display:flex;gap:8px}
.ca-card .cc-add{background:var(--bg);color:#fff;border:none;border-radius:8px;padding:6px 14px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}
.ca-card .cc-add:disabled{background:var(--ok);cursor:default}
.ca-card .cc-copy{background:none;border:1.5px solid var(--line);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer;font-family:inherit;color:var(--mut)}
.ca-card .cc-copy:hover{border-color:var(--bg);color:var(--bg)}
.ca-export{display:flex;gap:10px;margin-top:20px;flex-wrap:wrap}
.ca-export button{background:var(--card);border:1.5px solid var(--line);border-radius:10px;padding:10px 18px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;color:var(--ink)}
.ca-export button:hover{border-color:var(--bg);color:var(--bg)}
.ca-empty{text-align:center;padding:60px 20px;color:var(--mut);font-size:14px}
.ca-empty .ce-icon{font-size:40px;margin-bottom:12px}
@media(max-width:600px){.ca-stats{grid-template-columns:1fr 1fr}.ca-stat:last-child{grid-column:1/-1}}
/* тост-подтверждение действий без отдельного экрана */
.toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%) translateY(20px);background:#0C0608;color:#fff;padding:13px 20px;border-radius:13px;font-size:13.5px;font-weight:600;box-shadow:0 10px 30px rgba(0,0,0,.3);opacity:0;pointer-events:none;transition:all .25s;z-index:999;max-width:min(90vw,520px);display:flex;align-items:center;gap:9px}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.toast .tk{color:#FECDD3}
.docrow{cursor:pointer}.docrow:hover{color:var(--bg)}
/* ── дедлайн-виджет / следующий шаг ── */
.next-step{background:var(--tint);border:1.5px solid rgba(159,18,57,.25);border-radius:13px;padding:13px 16px;display:flex;align-items:center;gap:13px;max-width:760px;margin:14px 0 16px;cursor:pointer}
.next-step:hover{border-color:var(--bg);box-shadow:0 4px 14px rgba(159,18,57,.12)}
.ns-icon{font-size:22px;flex-shrink:0}
.ns-body{flex:1}
.ns-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;color:var(--bg);margin-bottom:2px}
.ns-text{font-size:13.5px;font-weight:600;color:var(--dark)}
.ns-sub{font-size:12px;color:var(--mut);margin-top:1px}
.ns-btn{background:var(--bg);color:#fff;border:none;border-radius:9px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0;font-family:inherit}
.ns-btn:hover{background:var(--bghv)}
/* ── таймлайн дела ── */
.tl-section{max-width:760px;margin:22px 0 0}
.tl-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;color:var(--mut);margin-bottom:14px}
.tl{display:flex;flex-direction:column;gap:0;position:relative;padding-left:22px}
.tl::before{content:'';position:absolute;left:7px;top:6px;bottom:6px;width:2px;background:var(--line)}
.tl-item{position:relative;padding:0 0 18px 18px}
.tl-item:last-child{padding-bottom:0}
.tl-dot{position:absolute;left:-8px;top:5px;width:14px;height:14px;border-radius:50%;background:var(--line);border:2px solid #fff;z-index:1;box-sizing:border-box}
.tl-item.done .tl-dot{background:var(--ok);border-color:#fff}
.tl-item.active .tl-dot{background:var(--bg);border-color:#fff;box-shadow:0 0 0 3px rgba(159,18,57,.18)}
.tl-item.pending .tl-dot{background:#fff;border:2px solid var(--line)}
.tl-date{font-size:11px;color:var(--mut);font-weight:600;margin-bottom:1px}
.tl-title{font-size:13px;font-weight:700;color:var(--dark)}
.tl-item.active .tl-title{color:var(--bg)}
.tl-item.pending .tl-title{color:var(--mut)}
.tl-ev{font-size:12px;color:var(--mut);margin-top:2px}
/* ── RESPONSIVE · MOBILE / TELEGRAM MINIAPP ── */
@media (max-width:600px){
/* HERO — мобиль: лого → фото → текст */
.hero{grid-template-columns:1fr;grid-template-rows:auto auto auto;padding:0 0 32px;gap:0;align-items:start}
.hero-logo{grid-column:1;grid-row:1;padding:20px 18px 0;margin-bottom:0}
.hero .face{grid-column:1;grid-row:2;margin:14px 0 0}
.hero-body{grid-column:1;grid-row:3;padding:20px 18px 0}
.hero .face img{height:240px;min-height:unset;border-radius:0;object-position:center 12%}
.hero .face .cap{padding:0 18px;color:rgba(255,255,255,.6)}
.hero h1{font-size:26px;letter-spacing:-.5px}
.hero p{font-size:14px;margin-bottom:20px}
.hero .cta{flex-direction:column}
.hero .cta .btn{width:100%;text-align:center}
/* TOPBAR */
.topbar{padding:10px 14px}
.topbar .ttl{font-size:12px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.topbar a{font-size:12px}
/* CHAT — убираем левый отступ под аватар на мобиле */
.chatwrap{padding:14px 12px 100px}
.intake{margin:4px 0 4px 0}
.deliverables{margin:4px 0 4px 0;max-width:100%}
.mode-badge{margin-left:0}
/* TOUCH TARGETS ≥ 48px */
.intake-opt{padding:14px 14px;min-height:52px}
.deliv{padding:14px 14px;min-height:56px}
.btn{min-height:48px}
/* ACTBAR — кнопки в колонку */
.actbar .inner{flex-direction:column;gap:8px}
.actbar .inner .btn{width:100%;text-align:center}
/* NEXT-STEP WIDGET — стек на мобиле */
.next-step{flex-wrap:wrap;gap:10px}
.ns-btn{width:100%;text-align:center}
.ns-body{min-width:0;width:100%}
/* PAY */
.pay{padding:20px 14px}
.pay h2{font-size:20px}
.plan-what{padding:12px 13px}
.plan-pitch{padding:12px 13px;flex-direction:row;align-items:flex-start}
/* STAT PILL — не перекрывает actbar */
.stats-pill{bottom:100px;right:10px;font-size:10px;min-width:140px}
/* ДОКУМЕНТЫ / КЕЙСЫ */
.tab-content{padding:14px 0}
/* КАБИНЕТ — мобиль: сайдбар скрыть, контент на всю ширину */
.app{flex-direction:column}
.side{width:100%;flex-direction:row;flex-wrap:wrap;padding:8px 0 0;gap:0;min-height:unset}
.side .lg{display:none}
.side .prof{display:none}
.side a{padding:8px 12px;font-size:12px;flex-direction:column;gap:2px;border-left:none;border-bottom:2px solid transparent;text-align:center;flex:1}
.side a.on{background:rgba(159,18,57,.2);border-left:none;border-bottom-color:var(--bg)}
.main{padding:16px 14px}
}
/* ── TELEGRAM MINIAPP ── */
/* Класс .tma навешивается JS-слоем ниже если запущен внутри TG */
.tma .actbar{padding-bottom:calc(16px + env(safe-area-inset-bottom,0px))}
.tma .topbar a.back-link{display:none} /* кнопка «назад» — нативная TG BackButton */
/* ── UPLOAD STEP ── */
.upload-zone{border:2px dashed rgba(159,18,57,.25);border-radius:16px;padding:28px 20px;text-align:center;background:var(--card);cursor:pointer;transition:border-color .2s,background .2s;margin:12px 0}
.upload-zone:hover{border-color:var(--bg);background:var(--tint)}
.uz-icon{font-size:32px;margin-bottom:10px}
.uz-title{font-size:15px;font-weight:700;color:var(--dark);margin-bottom:4px}
.uz-sub{font-size:12px;color:var(--mut)}
.upload-paste{width:100%;border:1.5px solid var(--line);border-radius:12px;padding:14px 16px;font-family:inherit;font-size:14px;color:var(--ink);background:var(--card);resize:vertical;min-height:90px;margin:8px 0;display:block}
.upload-paste:focus{outline:none;border-color:var(--bg)}
.upload-or{display:flex;align-items:center;gap:12px;color:var(--mut);font-size:12px;margin:4px 0}
.upload-or::before,.upload-or::after{content:'';flex:1;height:1px;background:var(--line)}
.upload-box{max-width:560px;margin:0 auto;padding:0 0 24px}
/* ── SCAN ANIMATION ── */
.scan-wrap{display:flex;flex-direction:column;align-items:center;padding:48px 24px 56px;text-align:center}
.scan-doc-outer{position:relative;width:170px;height:220px;margin:0 auto 32px}
.scan-doc-card{width:170px;height:220px;background:var(--card);border:1.5px solid var(--line);border-radius:14px;overflow:hidden;position:relative;box-shadow:0 12px 40px rgba(0,0,0,.12)}
.scan-doc-card::before{content:'';position:absolute;left:22px;right:22px;top:22px;height:7px;background:var(--line);border-radius:4px;
box-shadow:0 16px 0 var(--line),0 29px 0 var(--line),0 42px 0 var(--line),0 55px 0 rgba(159,18,57,.15),0 68px 0 var(--line),0 81px 0 var(--line),0 94px 0 var(--line),0 107px 0 rgba(159,18,57,.1),0 120px 0 var(--line),0 133px 0 rgba(159,18,57,.08)}
.scan-beam{position:absolute;left:0;right:0;height:3px;background:linear-gradient(90deg,transparent 0%,rgba(159,18,57,.2) 10%,var(--bg) 50%,rgba(159,18,57,.2) 90%,transparent 100%);box-shadow:0 0 14px 5px rgba(159,18,57,.28);animation:beamDown 2s ease-in-out infinite;z-index:2}
@keyframes beamDown{0%{top:-3px;opacity:0}6%{opacity:1}94%{opacity:1}100%{top:223px;opacity:0}}
.scan-av{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:68px;height:68px;border-radius:50%;border:4px solid #fff;object-fit:cover;object-position:center 12%;box-shadow:0 4px 16px rgba(0,0,0,.18);z-index:3;animation:avPulse 2.2s ease-in-out infinite}
@keyframes avPulse{0%,100%{box-shadow:0 4px 16px rgba(0,0,0,.18),0 0 0 0 rgba(159,18,57,.4)}60%{box-shadow:0 4px 16px rgba(0,0,0,.18),0 0 0 10px rgba(159,18,57,0)}}
.scan-label{font-size:18px;font-weight:700;color:var(--dark);min-height:28px;margin-bottom:7px;transition:opacity .25s}
.scan-hint{font-size:13px;color:var(--mut);margin-bottom:20px}
.scan-dots{display:flex;gap:8px;justify-content:center}
.scan-dot{width:7px;height:7px;border-radius:50%;background:var(--bg);opacity:.3;animation:dotB 1.4s ease-in-out infinite}
.scan-dot:nth-child(2){animation-delay:.22s}.scan-dot:nth-child(3){animation-delay:.44s}
@keyframes dotB{0%,100%{opacity:.25;transform:scale(1)}50%{opacity:1;transform:scale(1.35)}}
</style></head>
<body>
<!-- ═ 1. СТАРТ / ОФФЕР ═ -->
<section class="screen on" id="start">
<div class="hero">
<div class="hero-logo">
<img class="hero-logomark" src="logos/logo-wasrusgen1-real.svg" alt="@wasrusgen1">
<div class="hero-logo-sep"></div>
<img class="hero-wordmark" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА">
</div>
<div class="hero-body">
<!-- Новый клиент -->
<div id="hero-new">
<h1>Договор пишут юристы другой стороны. Кто защищает вас?</h1>
<div class="hero-chat" id="hero-chat">
<div class="hero-chat-hdr">
<img class="hero-chat-av" src="logos/elena-photo.jpg" alt="Елена">
<div>
<div class="hero-chat-name">Елена</div>
<div class="hero-chat-status"><span class="hero-chat-dot"></span>онлайн · отвечает сразу</div>
</div>
</div>
<div class="hero-chat-msgs" id="hchat-msgs"></div>
<div class="hero-chat-replies" id="hchat-replies" style="display:none">
<div class="hc-reply" onclick="heroChatReply('check')">📄 Дали договор на подпись</div>
<div class="hc-reply" onclick="heroChatReply('dispute')">🚨 Контрагент нарушает условия</div>
<div class="hc-reply" onclick="heroChatReply('create')">✍️ Нужно составить документ</div>
<div class="hc-reply" onclick="heroChatReply('question')">Не знаю с чего начать</div>
</div>
<div class="hero-chat-input-row" id="hchat-input-row" style="display:none">
<button class="hc-mic-btn" id="hcmic-btn" onclick="toggleVoice('hchat-inp','hcmic-btn')" title="Голосовой ввод">🎙</button>
<input class="hc-text-inp" id="hchat-inp" placeholder="Например: контрагент не платит / дали договор на подпись..."
onkeydown="if(event.key==='Enter')heroChatSend()">
<button class="hc-send-btn" onclick="heroChatSend()"></button>
</div>
</div>
<div class="cta"><button class="btn btn-p" onclick="go('elena')">Проверить договор бесплатно →</button></div>
<div class="priv">🔒 Без регистрации · данные у вас · первые 3 риска бесплатно</div>
<div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-o" style="font-size:13px;padding:8px 18px" onclick="go('cabinet')">📂 Войти в кабинет</button>
<button class="svc-btn-detail" style="font-size:12px" onclick="go('org-register')">🏢 Для организаций →</button>
</div>
</div>
<!-- Вернувшийся клиент -->
<div id="hero-returning" style="display:none">
<div class="ret-chat-stat-row" id="ret-stats-row"></div>
<div class="hero-chat ret-chat" id="ret-chat">
<div class="hero-chat-hdr">
<img class="hero-chat-av" src="logos/elena-photo.jpg" alt="Елена">
<div>
<div class="hero-chat-name">Елена</div>
<div class="hero-chat-status"><span class="hero-chat-dot"></span>онлайн · помню вас</div>
</div>
</div>
<div class="hero-chat-msgs" id="rchat-msgs"></div>
<div class="hero-chat-replies" id="rchat-replies" style="display:none">
<!-- заполняется динамически через _renderRchatButtons() -->
</div>
<div class="hero-chat-input-row" id="rchat-input-row" style="display:none">
<button class="hc-mic-btn" id="rcmic-btn" onclick="toggleVoice('rchat-inp','rcmic-btn')" title="Голосовой ввод">🎙</button>
<input class="hc-text-inp" id="rchat-inp" placeholder="Чем могу помочь сегодня?"
onkeydown="if(event.key==='Enter')retChatSend()">
<button class="hc-send-btn" onclick="retChatSend()"></button>
</div>
</div>
<div class="priv" style="margin-top:8px">🔒 Данные только у вас</div>
</div>
</div>
<div class="face"><img src="logos/elena-photo.jpg" alt="Елена"><div class="cap">Елена — ваш референт</div></div>
</div>
</section>
<!-- ═ 2. ЕЛЕНА / ТИЗЕР ═ -->
<section class="screen" id="elena">
<div class="topbar"><img class="topbar-wm" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА"><span class="sep"></span><span class="ttl" id="elena-ttl">Знакомство · Елена</span><span class="back back-link" onclick="go('start')">← в начало</span></div>
<div id="elena-mode-badge"></div>
<div class="chatwrap">
<!-- ШАГ 1: Диалог Елены — вход v4 -->
<div id="el-step1">
<!-- Приветствие убрано — оно уже было на первом экране -->
<div class="msg elena-anim" style="--d:.1s"><div class="av"><img src="logos/elena-photo.jpg"></div><div class="bubble"><div class="nm">Елена</div>Чем могу помочь?</div></div>
<div class="elena-dialog-wrap elena-anim" style="--d:.4s">
<div class="elena-dialog-row">
<input class="elena-main-inp" id="intake-custom" placeholder="Например: хочу проверить договор аренды..." onkeydown="if(event.key==='Enter')elenaIntentFromInput()">
<button class="voice-btn-sm" id="voice-btn-elena" onclick="toggleVoice('intake-custom')" title="Голосовой ввод">🎙</button>
<button class="elena-go-btn" onclick="elenaIntentFromInput()"></button>
</div>
</div>
<div class="elena-quick elena-anim" style="--d:1.95s">
<div class="elena-q-lbl">или выберите быстро:</div>
<div class="intent-grid">
<div class="int-card" onclick="elenaIntentGo('check')"><div class="int-ico">📄</div><div class="int-body"><div class="int-lbl">Проверить договор</div><div class="int-sub">Найду риски и объясню каждый пункт простым языком</div></div></div>
<div class="int-card" onclick="elenaIntentGo('create')"><div class="int-ico">✍️</div><div class="int-body"><div class="int-lbl">Составить документ</div><div class="int-sub">Договор, доверенность, претензию — под ваши параметры</div></div></div>
<div class="int-card" onclick="elenaIntentGo('question')"><div class="int-ico">💬</div><div class="int-body"><div class="int-lbl">Задать вопрос</div><div class="int-sub">Отвечу без загрузки документа — спросите текстом или голосом</div></div></div>
</div>
<div style="margin-top:8px;text-align:center">
<button class="svc-btn-detail" style="font-size:12px" onclick="go('cabinet')">🗂️ Я уже клиент — войти в кабинет</button>
</div>
</div>
</div>
<!-- ШАГ 1б: загрузка договора -->
<div id="el-step-upload" style="display:none">
<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div><div class="bubble"><div class="nm">Елена</div><span id="el-ack"></span></div></div>
<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div><div class="bubble"><div class="nm">Елена</div>
Отлично. Загрузите договор или вставьте текст — посмотрю и сразу скажу с чем имеем дело 📄
</div></div>
<div class="upload-box">
<div class="upload-zone" onclick="document.getElementById('el-paste').focus()">
<div class="uz-icon">📂</div>
<div class="uz-title">Перетащите файл сюда</div>
<div class="uz-sub">PDF · DOCX · TXT · данные остаются на вашем устройстве 🔒</div>
</div>
<div class="upload-or">или вставьте текст</div>
<textarea class="upload-paste" id="el-paste" placeholder="Вставьте текст договора..."></textarea>
<button class="btn btn-p" style="width:100%;margin-top:4px" onclick="startScan()">Проанализировать →</button>
</div>
</div>
<!-- ШАГ 1в: анимация сканирования -->
<div id="el-step-scan" style="display:none">
<div class="scan-wrap">
<div class="scan-doc-outer">
<div class="scan-doc-card"><div class="scan-beam"></div></div>
<img class="scan-av" src="logos/elena-photo.jpg" alt="Елена">
</div>
<div class="scan-label" id="scan-label">Читаю договор...</div>
<div class="scan-hint">Ищу риски и спорные условия</div>
<div class="scan-dots">
<div class="scan-dot"></div><div class="scan-dot"></div><div class="scan-dot"></div>
</div>
</div>
</div>
<!-- ШАГ 2: результаты анализа (скрыт до сканирования) -->
<div id="el-step2" style="display:none">
<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div><div class="bubble"><div class="nm">Елена</div>
Прочитала ваш договор 📄 Это <b id="el-scan-type">агентский договор</b>.<br>
<span id="el-ctype-note" class="ctype-note"></span>
Нашла <b>12 моментов</b>, из них <b>5 критичных</b>. <span id="el-intro-tail"></span> Показываю 3:
<div class="risk-mini">
<div class="rn">п.1.1 · ст. 429 ЗоЗПП</div>
<div id="r1-quote" class="risk-quote"></div>
<span id="r1-text"></span>
</div>
<div class="risk-mini">
<div class="rn">п.4.6 · ст. 330 ГК</div>
<div id="r2-quote" class="risk-quote"></div>
<span id="r2-text"></span>
</div>
<div class="risk-mini">
<div class="rn">ст. 19.1 ТК</div>
<div id="r3-quote" class="risk-quote"></div>
<span id="r3-text"></span>
</div>
<div class="lock" id="el-lock"></div>
</div></div>
<div id="el-pitch-msg" class="msg" style="display:none"><div class="av"><img src="logos/elena-photo.jpg"></div><div class="bubble"><div class="nm">Елена</div><span id="el-ctype-pitch"></span></div></div>
<span id="el-fork-q" style="display:none"></span>
<div class="deliverables">
<div id="deliv-protocol" class="deliv" onclick="selectDeliv('protocol')">
<span class="di">📋</span>
<div><div class="dn">Протокол разногласий</div><div class="dd2" id="dd2-protocol">Список спорных пунктов + зачем менять каждый</div><a class="sample-link" href="sample-protocol.html" target="_blank" onclick="event.stopPropagation()">📄 Образец →</a></div>
</div>
<div id="deliv-redact" class="deliv" onclick="selectDeliv('redact')">
<span class="di">✏️</span>
<div><div class="dn">Переработка с комментариями</div><div class="dd2" id="dd2-redact">Новая редакция каждого пункта + пояснение изменений</div><a class="sample-link" href="sample-redact.html" target="_blank" onclick="event.stopPropagation()">📄 Образец →</a></div>
</div>
<div id="deliv-clean" class="deliv" onclick="selectDeliv('clean')">
<span class="di"></span>
<div><div class="dn">Чистая редакция</div><div class="dd2" id="dd2-clean">Договор готов к подписанию — без лишних пояснений</div><a class="sample-link" href="sample-clean.html" target="_blank" onclick="event.stopPropagation()">📄 Образец →</a></div>
</div>
<div id="deliv-partner" class="deliv deliv-top" onclick="selectDeliv('partner')">
<span class="di">🤝</span>
<div><div class="dn">Партнёрская редакция</div><div class="dd2" id="dd2-partner">Вариант, который устроит обе стороны — без конфликта</div><a class="sample-link" href="sample-partner.html" target="_blank" onclick="event.stopPropagation()">📄 Образец →</a></div>
<span class="deliv-badge" id="deliv-rec-badge">Рекомендуем</span>
</div>
</div>
<div class="deliv-sep">или другой формат</div>
<div id="deliv-consult" class="deliv" onclick="selectDeliv('consult')">
<span class="di">💬</span>
<div><div class="dn">Консультация по договору</div><div class="dd2" id="dd2-consult">Задаёте вопросы — AI разбирает ваш договор и отвечает по вашей ситуации. Быстро, конкретно, без лишнего</div><span class="sample-link" style="cursor:default;border-color:transparent;color:var(--mut)">💬 чат · ответ за 2 часа</span></div>
</div>
<div id="deliv-reply" class="deliv" onclick="selectDeliv('reply')">
<span class="di">📨</span>
<div><div class="dn">Ответ контрагенту</div><div class="dd2" id="dd2-reply">Готовый текст ответа на отказ или встречные возражения — с аргументами и правовой позицией</div></div>
</div>
<!-- ── СВОЙ ЗАПРОС ── -->
<div class="custom-req-row">
<button class="custom-req-toggle" id="custom-req-btn" onclick="toggleCustomReq()">✏️ Нужен другой формат? Опишите задачу →</button>
</div>
<div id="custom-req-area" class="custom-req-area" style="display:none">
<div class="msg" style="margin-top:10px">
<div class="av"><img src="logos/elena-photo.jpg"></div>
<div class="bubble"><div class="nm">Елена</div>Опишите что нужно — голосом или текстом. Я передам юристу и мы свяжемся с вами в течение 24 часов.</div>
</div>
<div class="custom-input-block">
<textarea id="custom-text" placeholder="Например: мне нужна только таблица рисков без переработки текста, или нужен разбор одного конкретного пункта…" rows="3"></textarea>
<div class="custom-input-btns">
<button class="custom-voice-btn" id="custom-voice-btn" onclick="toggleVoice()" title="Диктовать голосом">🎤</button>
<button class="btn btn-p" onclick="submitCustomReq()" style="flex:1;font-size:14px">Отправить запрос</button>
</div>
</div>
<div id="custom-confirm" class="msg" style="display:none">
<div class="av"><img src="logos/elena-photo.jpg"></div>
<div class="bubble"><div class="nm">Елена</div><span id="custom-confirm-text"></span></div>
</div>
</div>
</div>
</div>
<div class="actbar" id="el-actbar" style="display:none"><div class="inner"><button class="btn btn-p" style="flex:1" onclick="go('pay')">Получить полный разбор</button></div></div>
<!-- Постоянный чат-бар — показывается когда el-step1 скрыт -->
<div id="el-chat-bar" style="display:none;position:sticky;bottom:0;background:#fff;border-top:1.5px solid var(--line);padding:12px 18px;z-index:10">
<div class="elena-dialog-row">
<input class="elena-main-inp" id="el-chat-inp"
placeholder="Напишите вопрос или описание ситуации..."
onkeydown="if(event.key==='Enter')_elChatSend()">
<button class="voice-btn-sm" onclick="toggleVoice('el-chat-inp')" title="Голосовой ввод">🎙</button>
<button class="elena-go-btn" onclick="_elChatSend()"></button>
</div>
</div>
</section>
<!-- ═ РЕГИСТРАЦИЯ ОРГАНИЗАЦИИ ═ -->
<section class="screen" id="org-register">
<div class="topbar"><img class="topbar-wm" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА">
<span class="ttl">Регистрация организации</span>
<span class="back back-link" onclick="go('start')">← назад</span>
</div>
<div style="max-width:520px;margin:0 auto;padding:24px 18px">
<div class="enote" style="margin-bottom:24px"><img src="logos/elena-photo.jpg">
<div class="et"><b>Корпоративный доступ.</b> Один кабинет на всю компанию — каждому сотруднику свой доступ, руководителю — общий дашборд.</div>
</div>
<div style="display:flex;flex-direction:column;gap:12px">
<div>
<div style="font-size:12px;font-weight:600;color:var(--mut);margin-bottom:4px">Организация</div>
<input class="elena-main-inp" id="reg-org-name" placeholder="ООО «Название» или ИП Фамилия">
</div>
<div>
<div style="font-size:12px;font-weight:600;color:var(--mut);margin-bottom:4px">ИНН</div>
<input class="elena-main-inp" id="reg-inn" placeholder="7812345678">
</div>
<div style="border-top:1px solid var(--line);padding-top:12px;margin-top:4px">
<div style="font-size:12px;font-weight:700;margin-bottom:8px">Администратор (вы)</div>
<div style="display:flex;flex-direction:column;gap:8px">
<input class="elena-main-inp" id="reg-admin-name" placeholder="Имя и фамилия">
<input class="elena-main-inp" id="reg-admin-email" placeholder="Email" type="email">
<input class="elena-main-inp" id="reg-admin-phone" placeholder="Телефон +7...">
</div>
</div>
<button class="btn btn-p" style="padding:12px;font-size:15px;margin-top:8px" onclick="_registerOrg()">
Зарегистрировать организацию →
</button>
<div style="text-align:center;font-size:13px;color:var(--mut)">
Уже зарегистрированы? <a style="color:var(--bg);cursor:pointer" onclick="_showOrgLogin()">Войти по токену</a>
</div>
</div>
<!-- Вход по токену -->
<div id="org-login-form" style="display:none;margin-top:20px;padding-top:20px;border-top:1px solid var(--line)">
<div style="font-size:13px;font-weight:600;margin-bottom:8px">Войти по invite-токену</div>
<div style="display:flex;gap:8px">
<input class="elena-main-inp" id="org-token-inp" placeholder="Вставьте токен из приглашения" style="flex:1">
<button class="btn btn-p" style="padding:10px 16px" onclick="_loginByToken()">Войти</button>
</div>
</div>
</div>
</section>
<!-- ═ 3. ОПЛАТА ═ -->
<section class="screen" id="pay">
<div class="topbar"><img class="topbar-wm" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА"><span class="ttl" id="pay-ttl">Выбор варианта</span><span class="back back-link" onclick="go('elena')">← назад</span></div>
<div class="pay">
<h2 id="pay-h2">Что сделаем по вашему договору</h2>
<div class="s" id="pay-sub">Агентский договор · риск 4/5 · 12 пунктов · цена под ваш случай</div>
<!-- Что конкретно делаем -->
<div class="plan-what">
<div class="plan-what-title">Что войдёт в работу по вашему договору</div>
<ul id="pay-what-list">
<li>п.1.1 — снять личную ответственность перед потребителем (ст. 429 ЗоЗПП)</li>
<li>п.4.6 — установить потолок ответственности = сумма вознаграждения</li>
<li>п.п. о режиме работы — переформулировать признаки самостоятельности (ст. 19.1 ТК)</li>
<li>+ ещё 9 пунктов из полного заключения</li>
</ul>
</div>
<!-- Баланс -->
<div class="pay-bal" id="pay-bal-block">
<div class="pay-bal-ico">💳</div>
<div class="pay-bal-body">
<div class="pay-bal-lbl">Ваш баланс</div>
<div class="pay-bal-val"><span id="pay-bal-credits">0</span> кредитов</div>
<div class="pay-bal-sub" id="pay-bal-sub">Пополните баланс ниже или оплатите разово</div>
</div>
<button class="pay-bal-use" id="pay-bal-use-btn" onclick="useBalance()" style="display:none">Списать 1 кредит</button>
</div>
<!-- Баннер цены запуска -->
<div id="launch-banner" style="background:var(--tint);border:1.5px solid rgba(159,18,57,.2);border-radius:12px;padding:10px 16px;margin-bottom:14px;display:flex;align-items:center;justify-content:space-between;gap:12px">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:16px">🚀</span>
<div>
<div style="font-size:13px;font-weight:700;color:#9f1239">Цена запуска</div>
<div style="font-size:12px;color:#6b7280">Специальные условия для первых клиентов</div>
</div>
</div>
<div style="text-align:right;flex-shrink:0">
<div style="font-size:11px;color:#6b7280">действует до</div>
<div style="font-size:13px;font-weight:700;color:#9f1239" id="launch-deadline">28 авг 2026</div>
</div>
</div>
<!-- Переключатель: разовая / подписка -->
<div class="pay-tabs">
<div class="pay-tab on" id="ptab-once" onclick="payTab('once')">Разовая покупка</div>
<div class="pay-tab" id="ptab-sub" onclick="payTab('sub')">Подписка 💛</div>
</div>
<!-- Разовая -->
<div class="pay-pane on" id="ppane-once">
<div class="pkg-grid">
<div class="pkg-card" id="pkg-1" onclick="selectPkg(1,249)">
<div class="pkg-top"><span class="pkg-name">Одна проверка</span><span class="pkg-price">249 ₽</span></div>
<div class="pkg-desc">1 кредит — только для этого договора</div>
<div class="pkg-per">249 ₽ за кредит</div>
</div>
<div class="pkg-card rec" id="pkg-2" onclick="selectPkg(2,490)">
<div class="pkg-top"><span class="pkg-name">Пакет Старт · 3 проверки</span><span class="pkg-price">490 ₽</span></div>
<div class="pkg-desc">3 кредита — первый сразу на этот договор</div>
<div class="pkg-per">163 ₽ за кредит · экономия 34%</div>
</div>
<div class="pkg-card" id="pkg-3" onclick="selectPkg(3,1290)">
<div class="pkg-top"><span class="pkg-name">Пакет Бизнес · 10 проверок</span><span class="pkg-price">1 290 ₽</span></div>
<div class="pkg-desc">10 кредитов — хватит на несколько месяцев</div>
<div class="pkg-per">129 ₽ за кредит · экономия 48%</div>
</div>
<div class="pkg-card" id="pkg-4" onclick="selectPkg(4,2990)">
<div class="pkg-top"><span class="pkg-name">Пакет Корпоратив · 30 проверок</span><span class="pkg-price">2 990 ₽</span></div>
<div class="pkg-desc">30 кредитов — для команды или активных сделок</div>
<div class="pkg-per">100 ₽ за кредит · экономия 60%</div>
</div>
</div>
</div>
<!-- Подписка -->
<div class="pay-pane" id="ppane-sub">
<div class="sub-grid">
<div class="sub-card" id="sub-1" onclick="selectSub(1,990)">
<div class="sub-top"><span class="sub-name">Старт</span><span class="sub-price">990 ₽<span class="sub-period">/мес</span></span></div>
<div class="sub-desc">До 10 проверок в месяц</div>
<div class="sub-feat"><span>Проверка рисков</span><span>Список изменений</span></div>
</div>
<div class="sub-card rec" id="sub-2" onclick="selectSub(2,1990)">
<div class="sub-top"><span class="sub-name">Бизнес</span><span class="sub-price">1 990 ₽<span class="sub-period">/мес</span></span></div>
<div class="sub-desc">До 30 проверок + комментарии Елены</div>
<div class="sub-feat"><span>Всё из Старта</span><span>Обоснование изменений</span><span>Сроки из договоров</span></div>
</div>
<div class="sub-card" id="sub-3" onclick="selectSub(3,4900)">
<div class="sub-top"><span class="sub-name">Безлимит</span><span class="sub-price">4 900 ₽<span class="sub-period">/мес</span></span></div>
<div class="sub-desc">Без ограничений · партнёрские редакции</div>
<div class="sub-feat"><span>Всё из Бизнес</span><span>Партнёрская редакция</span><span>Приоритетная поддержка</span></div>
</div>
</div>
<div class="pay-refund-note">Отмена подписки — в любой момент. Неиспользованный остаток возвращается пропорционально. <a href="#">Условия возврата →</a></div>
</div>
<!-- Аргументация Елены за 2 дорогих -->
<div class="plan-pitch">
<img src="logos/elena-photo.jpg">
<div class="plan-pitch-body" id="pay-pitch">
Протокол без обоснования — это список требований. Контрагент вправе просто отказать: ему не нужно объяснять почему.<br><br>
<b>С обоснованием</b> каждое требование опирается на закон или стандарт отрасли. Это не ваш каприз — это норма. По опыту, такие протоколы принимают в первом же раунде.<br><br>
<b>Партнёрская версия</b> идёт дальше: мы формулируем так, чтобы контрагенту тоже было выгодно согласиться. Договор, который хочется выполнять 💛
</div>
</div>
<!-- ── ВЫБОР ТИПА КЛИЕНТА ── -->
<div class="client-type-block" id="client-type-block">
<div class="client-type-hdr">Кто оплачивает?</div>
<div class="client-type-tabs">
<div class="ct-tab act" id="ctt-fl" onclick="setClientType('fl')">
<span class="ct-tab-ico">🙋</span>Физлицо
</div>
<div class="ct-tab" id="ctt-ip" onclick="setClientType('ip')">
<span class="ct-tab-ico">💼</span>ИП
</div>
<div class="ct-tab" id="ctt-ooo" onclick="setClientType('ooo')">
<span class="ct-tab-ico">🏢</span>ООО / АО
</div>
</div>
<!-- B2B форма (ИП и ООО) -->
<div class="b2b-form" id="b2b-form">
<!-- Общие поля -->
<div class="b2b-row" style="margin-bottom:10px">
<div class="b2b-field">
<label>Название / ФИО ИП</label>
<input id="b2b-name" placeholder="ООО «Ромашка» или ИП Иванов И.И.">
</div>
</div>
<div class="b2b-row two" style="margin-bottom:10px">
<div class="b2b-field">
<label>ИНН</label>
<input id="b2b-inn" placeholder="10 или 12 цифр" maxlength="12" inputmode="numeric">
<span class="b2b-hint">ИП — 12 цифр, ООО — 10 цифр</span>
</div>
<div class="b2b-field" id="b2b-kpp-wrap">
<label>КПП</label>
<input id="b2b-kpp" placeholder="9 цифр" maxlength="9" inputmode="numeric">
<span class="b2b-hint">Только для ООО / АО</span>
</div>
</div>
<div class="b2b-row" style="margin-bottom:10px">
<div class="b2b-field">
<label>Юридический адрес</label>
<input id="b2b-addr" placeholder="г. Москва, ул. Ленина, д. 1, оф. 10">
</div>
</div>
<div class="b2b-row" style="margin-bottom:0">
<div class="b2b-field">
<label>Email для документов (счёт, акт)</label>
<input id="b2b-email" type="email" placeholder="bухгалтерия@company.ru">
</div>
</div>
<hr class="b2b-divider">
<!-- Загрузка подтверждающих документов -->
<div class="b2b-docs-label">Подтверждающие документы <span style="font-weight:400;text-transform:none;letter-spacing:0">(необязательно)</span></div>
<div class="b2b-docs-note">Свидетельство ИП, выписка ЕГРЮЛ или приказ о назначении руководителя — ускорит оформление закрывающих документов.</div>
<div class="b2b-upload-area">
<!-- Загрузка файла -->
<div class="b2b-upload-btn" id="b2b-file-btn">
<input type="file" id="b2b-file-input" accept=".pdf,.jpg,.jpeg,.png,.docx"
multiple onchange="b2bFileSelected(this)">
<span class="ubtn-ico">📎</span>
<div class="ubtn-lbl">Загрузить файл</div>
<div class="ubtn-sub">PDF, JPG, PNG, DOCX</div>
</div>
<!-- Фото с камеры -->
<div class="b2b-upload-btn" id="b2b-camera-btn" onclick="b2bOpenCamera()">
<span class="ubtn-ico">📷</span>
<div class="ubtn-lbl">Сфотографировать</div>
<div class="ubtn-sub">Камера устройства</div>
</div>
</div>
<!-- Список загруженных файлов -->
<div class="b2b-files-list" id="b2b-files-list"></div>
<!-- Камера -->
<div class="b2b-camera-wrap" id="b2b-camera-wrap">
<video class="b2b-video" id="b2b-video" autoplay playsinline muted></video>
<canvas class="b2b-canvas" id="b2b-canvas"></canvas>
<div class="b2b-camera-controls">
<button class="b2b-snap-btn" onclick="b2bSnap()">📸 Сделать снимок</button>
<button class="b2b-cancel-btn" onclick="b2bCloseCamera()">Отмена</button>
</div>
</div>
<!-- Предпросмотр фото -->
<div class="b2b-photo-preview" id="b2b-photo-preview">
<img id="b2b-photo-img" src="" alt="Фото документа">
<button class="rm-photo" onclick="b2bRemovePhoto()" title="Удалить"></button>
</div>
</div><!-- /b2b-form -->
</div><!-- /client-type-block -->
<div class="field"><label>Куда прислать результат</label><input id="pay-contact" placeholder="Telegram или email для чека и документов"></div>
<div class="pdn">Нажимая «Оплатить», вы соглашаетесь с <a href="oferta.html" target="_blank">офертой</a> и <a href="privacy.html" target="_blank">обработкой ПДн</a>. Данные договора остаются на вашем устройстве.</div>
<button class="btn btn-p" id="pay-price-btn" style="width:100%" onclick="ykOpen()">Оплатить 490 ₽</button>
</div>
</section>
<!--СТАТУС ЗАКАЗА-->
<section class="screen" id="order-status">
<div class="topbar">
<img class="topbar-wm" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА">
<span class="ttl">Заказ принят</span>
<span class="back-link" onclick="go('cabinet')" style="font-size:13px;color:var(--bg);font-weight:600;cursor:pointer">Мои дела →</span>
</div>
<div class="os-wrap">
<div class="os-ok">
<div class="os-ok-icon"></div>
<div class="os-ok-ttl">Оплата подтверждена</div>
<div class="os-ok-sub" id="os-ok-sub">Заказ передан в работу</div>
</div>
<div class="os-card">
<div class="oc-ttl" id="os-svc">Экспертиза договора</div>
<div class="oc-plan" id="os-plan-name">Стандарт</div>
<div class="oc-price" id="os-price">2 480 ₽</div>
<div class="oc-id" id="os-orderid">Заказ #—</div>
</div>
<div class="os-steps">
<div class="os-step done">
<div class="os-dot"></div>
<div class="os-step-lbl">Оплата<br>получена</div>
</div>
<div class="os-step active" id="os-step2">
<div class="os-dot"></div>
<div class="os-step-lbl" id="os-step2-lbl">Юрист<br>работает</div>
</div>
<div class="os-step pending">
<div class="os-dot">3</div>
<div class="os-step-lbl" id="os-step3-lbl">Готово</div>
</div>
</div>
<div class="os-deadline">
<div class="od-icon"></div>
<div class="od-text">
<strong id="os-deadline-ttl">Срок: до 24 часов</strong>
<span id="os-deadline-sub">после получения файла договора</span>
</div>
</div>
<div class="enote" style="margin-bottom:20px">
<img src="logos/elena-photo.jpg">
<div class="et" id="os-elena-msg">
Заказ получен — уже передала юристу. <b>Пришлите договор</b> в Telegram
<a href="https://t.me/VASRUSGEN" target="_blank">@wasrusgen1</a>
без него начать не получится. Как только файл придёт — сразу приступаем 💛
</div>
</div>
<div class="os-actions">
<a class="os-tg" href="https://t.me/VASRUSGEN" target="_blank">💬 Написать в Telegram</a>
<button class="btn btn-o" onclick="go('cabinet')">📂 Мои дела</button>
</div>
</div>
</section>
<!-- ═ АНАЛИТИКА CUSTOM-ЗАПРОСОВ ═ -->
<section class="screen" id="custom-admin">
<div class="topbar">
<img class="topbar-wm" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА">
<span class="ttl">Аналитика: свои запросы</span>
<span class="back back-link" onclick="history.back();go('start')">← назад</span>
</div>
<div class="ca-wrap">
<div class="ca-title">Нестандартные запросы</div>
<div class="ca-sub">Что клиенты просят, чего нет в системе — основа для расширения CTYPES и deliverables</div>
<div id="ca-body"></div>
</div>
</section>
<!-- ═ 47. КАБИНЕТ (вкладки) ═ -->
<section class="screen" id="cabinet">
<div class="app">
<aside class="side">
<div class="lg"><img class="topbar-wm" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА"></div>
<a id="t-cases" class="on" onclick="tab('cases')">🗂️ Мои дела</a>
<a id="t-case" onclick="tab('case')">📄 Дело: Кухня</a>
<a id="t-sroki" onclick="tab('sroki')">⏱️ Сроки</a>
<a id="t-shab" onclick="tab('shab')">📋 Шаблоны</a>
<a id="t-create" onclick="tab('create')">✍️ Составить документ</a>
<a id="t-docs" onclick="tab('docs')">✅ Мои документы</a>
<a id="t-casemap" onclick="tab('casemap')">📝 Карта дела</a>
<a id="t-team" onclick="tab('team')" style="display:none">👥 Команда</a>
<a id="t-requisites" onclick="tab('requisites')">🖊️ Реквизиты</a>
<a id="t-balance" onclick="tab('balance')">💳 Баланс и оплата</a>
<a onclick="go('start')">↩ Выйти (в начало)</a>
<a onclick="go('admin')" style="color:#ef4444;font-weight:700">⚙️ Администратор</a>
<div class="side-clock">
<div class="side-clock-day" id="sc-day">Среда</div>
<div class="side-clock-date" id="sc-date">28 мая 2025</div>
<div class="side-clock-time" id="sc-time">10:52:00</div>
</div>
<div class="prof"><div class="pa">РВ</div><div><div class="pn">Руслан Васильев</div><div class="pt">Тариф: Старт</div></div></div>
</aside>
<main class="main">
<!-- Мои дела -->
<div class="tabpane on" id="p-cases">
<div class="main-body">
<div class="crumb">Кабинет</div><h1>Мои дела</h1>
<!-- KPI -->
<div class="kpi-row">
<div class="kpi-card kpi-total"><div class="kc-ico">📁</div><div><div class="kc-num" id="kpi-total">5</div><div class="kc-lbl">Всего дел</div></div></div>
<div class="kpi-card kpi-work"><div class="kc-ico">🔵</div><div><div class="kc-num" id="kpi-work">3</div><div class="kc-lbl">В работе</div></div></div>
<div class="kpi-card kpi-urg"><div class="kc-ico">⚠️</div><div><div class="kc-num" id="kpi-urg">1</div><div class="kc-lbl">Срочных</div></div></div>
<div class="kpi-card kpi-done"><div class="kc-ico"></div><div><div class="kc-num" id="kpi-done">2</div><div class="kc-lbl">Завершено</div></div></div>
</div>
<!-- Фильтры -->
<div class="ct-filters">
<button class="ct-fbtn act" onclick="ctFilter('all',this)">Все</button>
<button class="ct-fbtn" onclick="ctFilter('active',this)">🟢 Активные</button>
<button class="ct-fbtn" onclick="ctFilter('dispute',this)">⚔️ В споре</button>
<button class="ct-fbtn" onclick="ctFilter('done',this)">✅ Завершённые</button>
<div class="ct-filter-sep"></div>
<button class="ct-fbtn" onclick="ctFilter('risk',this)">⚠ Срочные</button>
</div>
<!-- Таблица -->
<table class="ct-table" id="ct-table">
<thead>
<tr>
<th onclick="ctSort('name',this)">Договор</th>
<th onclick="ctSort('type',this)">Тип</th>
<th onclick="ctSort('date',this)">Дата</th>
<th onclick="ctSort('risk',this)">Риск</th>
<th onclick="ctSort('status',this)">Статус</th>
<th></th>
</tr>
</thead>
<tbody id="ct-tbody"></tbody>
</table>
</div><!-- /main-body -->
</div>
<!-- Внутри дела — CRM -->
<div class="tabpane" id="p-case"><div class="main-body">
<div class="crumb" style="margin-bottom:14px">Кабинет / <span style="cursor:pointer;color:var(--bg)" onclick="tab('cases')">Мои дела</span> / Кухня — агентский</div>
<!-- Шапка дела -->
<div class="case-hdr">
<div class="case-hdr-ico">🍽️</div>
<div class="case-hdr-info">
<div class="case-hdr-name">Кухня — агентский договор (ЗОВ)</div>
<div class="case-hdr-meta">
<span class="chip d">⚠ Высокий риск</span>
<span class="chip w">🔵 В работе</span>
<span class="chip n">12 рисков</span>
<span class="chip n">Агентский · 23.05.2025</span>
</div>
</div>
<div class="case-hdr-actions">
<button class="btn btn-o" style="font-size:12px;padding:7px 14px" onclick="toast('⬇️ PDF экспортируется')">⬇ PDF</button>
<button class="btn btn-p" style="font-size:12px;padding:7px 14px" onclick="toast('💬 Открываю чат с Еленой')">💬 Елена</button>
</div>
</div>
<!-- ⚡ Следующий шаг — ГЛАВНЫЙ БЛОК -->
<div class="next-step-v2" onclick="toast('📋 Открываю протокол разногласий для согласования')">
<div class="ns2-pulse"></div>
<div class="ns2-body">
<div class="ns2-label">Следующий шаг</div>
<div class="ns2-action">Отправить протокол разногласий контрагенту<span class="ns2-deadline">до 29.05</span></div>
<div class="ns2-meta">Протокол готов · 5 пунктов · ст. 22 ЗоЗПП · осталось 1 день</div>
</div>
<button class="ns2-btn" onclick="event.stopPropagation();toast('✅ Протокол согласован и готов к отправке')">Согласовать →</button>
</div>
<!-- Панель протокола (появляется когда выбраны пункты) -->
<div class="protocol-bar" id="protocol-bar">
<div class="pb-ico">📋</div>
<div class="pb-body">
<div class="pb-title">Протокол разногласий</div>
<div class="pb-sub" id="pb-sub">Выберите пункты во вкладке Риски</div>
</div>
<button class="pb-btn" id="pb-generate" onclick="toast('📋 Протокол разногласий формируется — Елена подготовит документ в течение 2 минут')">Сформировать протокол →</button>
</div>
<!-- Вкладки -->
<div class="case-tabs">
<div class="case-tab on" onclick="caseTab('overview',this)">Обзор</div>
<div class="case-tab" onclick="caseTab('risks',this)">Риски <span style="background:#fee2e2;color:#991b1b;border-radius:8px;padding:1px 6px;font-size:10px;font-weight:700">12</span></div>
<div class="case-tab" onclick="caseTab('docs',this)">Документы <span style="background:#e5e7eb;border-radius:8px;padding:1px 6px;font-size:10px">5</span></div>
<div class="case-tab" onclick="caseTab('timeline',this)">История</div>
<div class="case-tab" onclick="caseTab('chat',this)">Переписка <span style="background:#dbeafe;color:#1d4ed8;border-radius:8px;padding:1px 6px;font-size:10px;font-weight:700">1</span></div>
</div>
<!-- Обзор -->
<div class="case-pane on" id="cp-overview">
<!-- Прогресс дела -->
<div class="case-progress">
<div class="cp-step done">
<div class="cps-dot"></div>
<div class="cps-lbl">Загружен</div>
<div class="cps-date">23.05</div>
</div>
<div class="cp-step done">
<div class="cps-dot"></div>
<div class="cps-lbl">Экспертиза</div>
<div class="cps-date">23.05</div>
</div>
<div class="cp-step done">
<div class="cps-dot"></div>
<div class="cps-lbl">Новая редакция</div>
<div class="cps-date">24.05</div>
</div>
<div class="cp-step act">
<div class="cps-dot"></div>
<div class="cps-lbl">Протокол</div>
<div class="cps-date">сейчас</div>
</div>
<div class="cp-step pend">
<div class="cps-dot"></div>
<div class="cps-lbl">Ответ контрагенту</div>
<div class="cps-date">до 29.05</div>
</div>
<div class="cp-step pend">
<div class="cps-dot"></div>
<div class="cps-lbl">Подписание</div>
<div class="cps-date"></div>
</div>
</div>
<div class="overview-grid">
<div class="ov-card">
<div class="ov-card-ttl">Параметры дела</div>
<div class="ov-row"><span class="ov-k">Тип договора</span><span class="ov-v">Агентский</span></div>
<div class="ov-row"><span class="ov-k">Дата загрузки</span><span class="ov-v">23.05.2025</span></div>
<div class="ov-row"><span class="ov-k">Риск</span><span class="ov-v"><span class="chip d" style="font-size:11px">⚠ Высокий · 3/5</span></span></div>
<div class="ov-row"><span class="ov-k">Рисков найдено</span><span class="ov-v">12 (5 критичных)</span></div>
<div class="ov-row"><span class="ov-k">Статус</span><span class="ov-v">🔵 В работе</span></div>
</div>
<div class="ov-card">
<div class="ov-card-ttl">Текущий этап</div>
<div class="ov-row"><span class="ov-k">✅ Экспертиза</span><span class="ov-v" style="color:#16a34a">Готово</span></div>
<div class="ov-row"><span class="ov-k">✅ Новая редакция</span><span class="ov-v" style="color:#16a34a">Получена</span></div>
<div class="ov-row"><span class="ov-k">⏳ Протокол</span><span class="ov-v" style="color:#d97706">На согласовании</span></div>
<div class="ov-row"><span class="ov-k">⬜ Ответ контрагенту</span><span class="ov-v" style="color:#9ca3af">Ожидает</span></div>
<div class="ov-row"><span class="ov-k">⬜ Подписание</span><span class="ov-v" style="color:#9ca3af">Ожидает</span></div>
</div>
</div>
<div class="enote" style="margin-top:4px"><img src="logos/elena-photo.jpg"><div class="et">Протокол разногласий готов — осталось согласовать и отправить контрагенту до 29.05. Я напомню накануне 💛</div></div>
</div>
<!-- Риски -->
<div class="case-pane" id="cp-risks">
<div class="risk-summary">
<div class="risk-sum-item crit">🔴 5 критических</div>
<div class="risk-sum-item mid">🟡 4 средних</div>
<div class="risk-sum-item low">🟢 3 низких</div>
</div>
<div class="risk-list">
<div class="risk-item crit" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge crit">🔴 Критический</span>
<span class="risk-num">#1</span>
<span class="risk-title">Одностороннее изменение условий</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 4.2 договора · ст. 450 ГК РФ</div>
<div class="risk-rec">Агент вправе менять вознаграждение без вашего согласования.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Агент вправе в одностороннем порядке изменять размер агентского вознаграждения, уведомив Принципала не менее чем за 3 (три) календарных дня»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Изменение размера агентского вознаграждения допускается исключительно по письменному соглашению сторон, подписанному обеими сторонами»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(1,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item crit" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge crit">🔴 Критический</span>
<span class="risk-num">#2</span>
<span class="risk-title">Неограниченная ответственность принципала</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 8.1 договора · ст. 393 ГК РФ</div>
<div class="risk-rec">Размер убытков, которые агент может взыскать с вас, не ограничен.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Принципал возмещает Агенту убытки в полном объёме, включая упущенную выгоду, понесённые вследствие ненадлежащего исполнения обязательств»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Ответственность Принципала ограничена суммой агентского вознаграждения за 3 (три) последних месяца. Упущенная выгода возмещению не подлежит»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(2,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item crit" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge crit">🔴 Критический</span>
<span class="risk-num">#3</span>
<span class="risk-title">Автопролонгация без уведомления</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 9.3 договора · ст. 621 ГК РФ</div>
<div class="risk-rec">Договор продлевается на год автоматически — без явного согласия.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Договор считается пролонгированным на аналогичный срок, если ни одна из сторон не заявила письменного отказа»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Пролонгация допускается только при наличии письменного согласия обеих сторон, подписанного не позднее чем за 30 дней до окончания срока договора»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(3,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item crit" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge crit">🔴 Критический</span>
<span class="risk-num">#4</span>
<span class="risk-title">Отсутствие порядка сдачи-приёмки</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 5 договора · ст. 720 ГК РФ</div>
<div class="risk-rec">Нет формы акта и сроков — суд может признать услугу принятой по умолчанию.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Результат исполнения агентского поручения считается принятым по истечении 5 рабочих дней с момента получения отчёта при отсутствии замечаний»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Приёмка исполнения оформляется актом по форме Приложения №2. Акт подписывается в течение 3 рабочих дней. При наличии замечаний составляется мотивированный отказ в той же форме»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(4,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item crit" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge crit">🔴 Критический</span>
<span class="risk-num">#5</span>
<span class="risk-title">Невыгодная подсудность</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 11.2 договора · ст. 37 АПК РФ</div>
<div class="risk-rec">Споры рассматриваются по месту агента — вам придётся судиться в другом городе.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Все споры, вытекающие из настоящего договора, рассматриваются в Арбитражном суде города Москвы»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Все споры, вытекающие из настоящего договора, рассматриваются в арбитражном суде по месту нахождения Принципала»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(5,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item mid" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge mid">🟡 Средний</span>
<span class="risk-num">#6</span>
<span class="risk-title">Размытые сроки отчётности агента</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 6.1 договора · ст. 1008 ГК РФ</div>
<div class="risk-rec">«В разумный срок» — нет конкретной даты предоставления отчётов.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Агент представляет отчёт об исполнении поручения в разумный срок после окончания отчётного периода»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Агент представляет отчёт не позднее 5-го числа месяца, следующего за отчётным, с приложением первичных документов»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(6,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item mid" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge mid">🟡 Средний</span>
<span class="risk-num">#7</span>
<span class="risk-title">Нет срока уведомления при расторжении</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 9.2 договора · ст. 1010 ГК РФ</div>
<div class="risk-rec">Порядок уведомления не прописан — расторжение может прийти в любой момент.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Каждая из сторон вправе отказаться от договора, уведомив другую сторону о своём намерении»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Каждая из сторон вправе отказаться от договора, направив письменное уведомление не менее чем за 30 (тридцать) календарных дней до предполагаемой даты расторжения»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(7,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item mid" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge mid">🟡 Средний</span>
<span class="risk-num">#8</span>
<span class="risk-title">Вознаграждение без указания НДС</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 3.1 договора · НК РФ ст. 168</div>
<div class="risk-rec">Риск доначисления НДС и штрафов при налоговой проверке.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Размер агентского вознаграждения составляет 150 000 (сто пятьдесят тысяч) рублей ежемесячно»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Размер агентского вознаграждения составляет 150 000 (сто пятьдесят тысяч) рублей ежемесячно, в том числе НДС 20% — 25 000 рублей» <i>(или: «НДС не облагается — основание: применение УСН»)</i></div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(8,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item mid" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge mid">🟡 Средний</span>
<span class="risk-num">#9</span>
<span class="risk-title">Право субагентирования без согласования</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 3.4 договора · ст. 1009 ГК РФ</div>
<div class="risk-rec">Агент привлекает субагентов без вашего ведома — вы теряете контроль.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Агент вправе для исполнения поручения привлекать третьих лиц (субагентов) с уведомлением Принципала»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Привлечение субагентов допускается исключительно с предварительного письменного согласия Принципала в каждом конкретном случае»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(9,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item low" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge low">🟢 Низкий</span>
<span class="risk-num">#10</span>
<span class="risk-title">Расплывчатый перечень форс-мажора</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 10 договора · ст. 401 ГК РФ</div>
<div class="risk-rec">Перечень открытый — контрагент может сослаться на любое «чрезвычайное обстоятельство».</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Стороны освобождаются от ответственности при наступлении обстоятельств непреодолимой силы и иных чрезвычайных обстоятельств»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Форс-мажорными признаются исключительно: стихийные бедствия, военные действия, режим ЧС, эпидемия по решению ВОЗ, запретительные акты органов государственной власти. Перечень является исчерпывающим»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(10,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item low" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge low">🟢 Низкий</span>
<span class="risk-num">#11</span>
<span class="risk-title">Нет упоминания ЭДО и ЭЦП</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 12 договора · 63-ФЗ</div>
<div class="risk-rec">При дистанционной работе электронные документы могут не иметь юридической силы.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Уведомления и документы направляются сторонами по почте заказным письмом или курьерской службой»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«Стороны вправе использовать электронный документооборот с применением квалифицированной электронной подписи (КЭП) в соответствии с 63-ФЗ. Документы в ЭДО имеют равную юридическую силу с бумажными оригиналами»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(11,this)">Применить в протоколе →</button>
</div>
</div>
<div class="risk-item low" onclick="toggleRisk(this)">
<div class="risk-item-hdr">
<span class="risk-badge low">🟢 Низкий</span>
<span class="risk-num">#12</span>
<span class="risk-title">Нет обязанности уведомлять об изменении реквизитов</span>
<span class="risk-toggle"></span>
</div>
<div class="risk-norm">п. 12.3 договора</div>
<div class="risk-rec">Смена реквизитов без предупреждения — оплата уйдёт не туда, но вы останетесь должником.</div>
<div class="risk-expand">
<div class="risk-quote"><div class="risk-quote-lbl">📄 Как написано сейчас</div><div class="risk-quote-txt">«Стороны используют реквизиты, указанные в разделе 12 настоящего договора»</div></div>
<div class="risk-fix"><div class="risk-fix-lbl">✅ Рекомендуемая формулировка</div><div class="risk-fix-txt">«При изменении банковских реквизитов сторона обязана письменно уведомить контрагента не менее чем за 5 (пять) рабочих дней. Платёж по старым реквизитам после получения уведомления не признаётся надлежащим исполнением»</div></div>
<button class="risk-apply-btn" onclick="event.stopPropagation();applyRisk(12,this)">Применить в протоколе →</button>
</div>
</div>
</div>
</div>
<!-- Документы -->
<div class="case-pane" id="cp-docs">
<div class="doc-upload-btn" onclick="toast('📎 Выберите файл — договор, письмо или любой документ по делу. Данные остаются на вашем устройстве')">
<div class="doc-upload-ico">📎</div>
<div class="doc-upload-txt">Загрузить документ в дело
<span>PDF, DOCX, JPG · до 50 МБ · данные остаются на вашем устройстве</span>
</div>
</div>
<div style="max-width:700px">
<div class="docrow" onclick="toast('Открываю исходный договор v1')">📄 Исходный договор <span class="ver">v1</span></div>
<div class="docrow" onclick="toast('Открываю редакцию v2')">📝 Новая редакция <span class="ver">v2</span></div>
<div class="docrow" onclick="toast('Открываю заключение экспертизы — 12 рисков')">🔍 Заключение экспертизы <span class="ver">готово</span></div>
<div class="docrow" onclick="toast('Открываю протокол разногласий')">📋 Протокол разногласий <span class="ver">черновик</span></div>
<div class="docrow" onclick="toast('Открываю ответ контрагента')">⬇️ Ответ контрагента <span class="ver">входящее</span></div>
</div>
</div>
<!-- Переписка -->
<div class="case-pane" id="cp-chat">
<!-- Форма составления письма -->
<div class="chat-compose">
<div class="chat-compose-ttl">✉️ Составить письмо контрагенту</div>
<div class="chat-field">
<label>Кому</label>
<input type="text" value="ООО «Зов Ресторанс» — info@zov-rest.ru" readonly style="color:var(--mut);background:#f9fafb">
</div>
<div class="chat-field">
<label>Тема</label>
<input type="text" id="chat-subject" value="Протокол разногласий к агентскому договору от 20.05.2025">
</div>
<div class="chat-field">
<label>Текст письма</label>
<textarea id="chat-body" rows="6" style="font-size:12px;line-height:1.6">Уважаемые коллеги,
Направляем протокол разногласий к агентскому договору от 20.05.2025 г. Просим рассмотреть предложенные изменения и сообщить о Вашем решении в срок до 29.05.2025.
Прилагаем: протокол разногласий (1 л.).
С уважением,
Васильев Руслан Геннадьевич
ИП Васильев Р.Г.</textarea>
</div>
<div class="chat-actions">
<button class="chat-send-btn" onclick="toast('📧 Письмо подготовлено — скопируйте и отправьте из вашей почты. Запись о письме сохранена в истории дела')">Подготовить к отправке</button>
<button class="chat-copy-btn" onclick="toast('📋 Текст письма скопирован в буфер обмена')">Копировать текст</button>
</div>
</div>
<!-- История переписки -->
<div class="chat-list">
<div class="chat-list-ttl">История переписки</div>
<div class="chat-item inc" onclick="toast('📥 Открываю входящее письмо от контрагента')">
<div class="chat-arrow"></div>
<div class="chat-item-body">
<div class="chat-item-hdr">
<span class="chat-item-subj">Ответ на протокол разногласий</span>
<span class="chat-status new">Новое</span>
<span class="chat-item-date">24.05</span>
</div>
<div class="chat-item-prev">ООО Зов Ресторанс: «Готовы согласовать пп. 1,3,5. По п. 2 просим сохранить текущую редакцию...»</div>
</div>
</div>
<div class="chat-item out" onclick="toast('📤 Открываю отправленное письмо')">
<div class="chat-arrow"></div>
<div class="chat-item-body">
<div class="chat-item-hdr">
<span class="chat-item-subj">Протокол разногласий (черновик)</span>
<span class="chat-status sent">Отправлено</span>
<span class="chat-item-date">23.05</span>
</div>
<div class="chat-item-prev">Направляем протокол разногласий к агентскому договору от 20.05.2025...</div>
</div>
</div>
<div class="chat-item out" onclick="toast('📤 Открываю запрос реквизитов')">
<div class="chat-arrow"></div>
<div class="chat-item-body">
<div class="chat-item-hdr">
<span class="chat-item-subj">Запрос реквизитов и ИНН</span>
<span class="chat-status read">Прочитано</span>
<span class="chat-item-date">21.05</span>
</div>
<div class="chat-item-prev">В целях проверки контрагента просим предоставить полные реквизиты...</div>
</div>
</div>
</div>
</div>
<!-- История -->
<div class="case-pane" id="cp-timeline">
<div class="tl-section" style="margin-top:0">
<div class="tl">
<div class="tl-item done"><div class="tl-dot"></div><div class="tl-date">23.05</div><div class="tl-title">Договор загружен</div><div class="tl-ev">Агентский договор ЗОВ — v1</div></div>
<div class="tl-item done"><div class="tl-dot"></div><div class="tl-date">23.05</div><div class="tl-title">Экспертиза завершена</div><div class="tl-ev">12 рисков, 5 критичных — заключение готово</div></div>
<div class="tl-item done"><div class="tl-dot"></div><div class="tl-date">24.05</div><div class="tl-title">Получена новая редакция</div><div class="tl-ev">Контрагент прислал v2 — сверка выполнена</div></div>
<div class="tl-item active"><div class="tl-dot"></div><div class="tl-date">сейчас</div><div class="tl-title">Протокол разногласий</div><div class="tl-ev">Черновик готов · ждёт согласования</div></div>
<div class="tl-item pending"><div class="tl-dot"></div><div class="tl-date">до 29.05</div><div class="tl-title">Ответ контрагенту</div><div class="tl-ev">Направить протокол разногласий</div></div>
<div class="tl-item pending"><div class="tl-dot"></div><div class="tl-date"></div><div class="tl-title">Итоговое подписание</div><div class="tl-ev">После согласования всех пунктов</div></div>
</div>
</div>
</div>
</div>
</div>
<!-- Сроки — Gantt -->
<div class="tabpane" id="p-sroki"><div class="main-body"><div class="crumb">Кабинет</div><h1>Сроки</h1>
<div class="enote" style="margin-bottom:20px"><img src="logos/elena-photo.jpg"><div class="et"><b>Слежу за сроками из ваших договоров.</b> Если в документе есть даты оплат, уведомлений или расторжения — покажу здесь 💛</div></div>
<div class="dl-summary" id="dl-summary"></div>
<div class="dl-filter">
<div class="dl-ftab on" onclick="dlFilter('all',this)">Все</div>
<div class="dl-ftab" onclick="dlFilter('urgent',this)">Срочные 🔴🟡</div>
<div class="dl-ftab" onclick="dlFilter('done',this)">Выполненные</div>
</div>
<div class="dl-list" id="dl-list"></div>
</div></div>
<!-- Шаблоны -->
<div class="tabpane" id="p-shab"><div class="main-body">
<div class="crumb">Кабинет</div><h1>Шаблоны</h1>
<div class="enote"><img src="logos/elena-photo.jpg">
<div class="et"><b>Заполню под ваш случай автоматически.</b> Данные из ваших договоров — уточню только что нужно 💛</div>
</div>
<!-- Контекстные шаблоны — рендерятся динамически -->
<div id="shab-context" style="margin-bottom:16px"></div>
<!-- Все шаблоны -->
<div class="elena-q-lbl" style="margin-bottom:10px">Все шаблоны:</div>
<div class="tpls" id="shab-all">
<div class="tpl" onclick="_startTemplate('notice_no_renewal')">
<div class="ti">📬</div><div class="tn">Уведомление о непродлении</div>
<div class="td">аренда · за 30 дней до окончания</div>
</div>
<div class="tpl" onclick="_startTemplate('claim_payment')">
<div class="ti">✉️</div><div class="tn">Претензия об оплате</div>
<div class="td">взыскать долг · ст. 395 ГК</div>
</div>
<div class="tpl" onclick="_startTemplate('claim_quality')">
<div class="ti">⚠️</div><div class="tn">Претензия о качестве</div>
<div class="td">товар / работы · ЗоЗПП / ст. 723 ГК</div>
</div>
<div class="tpl" onclick="_startTemplate('response_claim')">
<div class="ti">🛡️</div><div class="tn">Ответ на претензию</div>
<div class="td">отбить требование · с аргументами</div>
</div>
<div class="tpl" onclick="_startTemplate('deposit_return')">
<div class="ti">💰</div><div class="tn">Требование о возврате депозита</div>
<div class="td">после окончания аренды</div>
</div>
<div class="tpl" onclick="_startTemplate('termination_agreement')">
<div class="ti">🚪</div><div class="tn">Соглашение о расторжении</div>
<div class="td">по соглашению сторон · без суда</div>
</div>
<div class="tpl" onclick="_startTemplate('act_acceptance')">
<div class="ti"></div><div class="tn">Акт приёмки-передачи</div>
<div class="td">подряд / услуги · фиксируем факт</div>
</div>
<div class="tpl" onclick="_startTemplate('power_of_attorney')">
<div class="ti">📑</div><div class="tn">Доверенность</div>
<div class="td">в суд · на авто · для сделок</div>
</div>
</div>
</div></div>
<!-- Мои документы — чеклист аудита -->
<div class="tabpane" id="p-docs"><div class="main-body">
<div class="crumb">Кабинет</div><h1>Мои документы</h1>
<div class="enote"><img src="logos/elena-photo.jpg">
<div class="et"><b>Аудит документов по Вашей ситуации.</b> Отмечайте что есть — покажу что грозит если чего-то не хватает 💛</div>
</div>
<div id="docs-checklist">
<!-- Заполняется через renderDocChecklist() -->
<div style="padding:24px;text-align:center;color:var(--mut)">
Загрузите договор или опишите ситуацию Елене — она определит тип и покажет нужный чеклист.
</div>
</div>
</div></div>
<!-- Команда — дашборд менеджера -->
<div class="tabpane" id="p-team"><div class="main-body">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
<h1 style="margin:0">👥 Команда</h1>
<button class="btn btn-p" style="padding:7px 14px;font-size:13px" onclick="_showInviteModal()">+ Пригласить</button>
</div>
<div id="team-dashboard">
<div style="text-align:center;padding:32px;color:var(--mut)">Загрузка...</div>
</div>
</div></div>
<!-- Карта дела — примечания, риски, обещания -->
<div class="tabpane" id="p-casemap"><div class="main-body">
<div class="crumb">Кабинет</div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
<h1 style="margin:0">📝 Карта дела</h1>
<button class="svc-btn-detail" style="font-size:12px" onclick="_exportCaseMap()">📄 Скачать PDF</button>
</div>
<div class="enote" style="margin-bottom:20px"><img src="logos/elena-photo.jpg">
<div class="et">Здесь собрано всё что важно знать по делу: принятые решения, зафиксированные риски, обещания по документам. <b>Это приложение к делу — не сам документ.</b></div>
</div>
<div id="casemap-content">
<!-- Заполняется через renderCaseMap() -->
<div style="text-align:center;padding:32px;color:var(--mut)">
Начните работу с Еленой — карта дела заполнится автоматически.
</div>
</div>
</div></div>
<!-- Реквизиты — подпись и печать -->
<div class="tabpane" id="p-requisites"><div class="main-body">
<div class="crumb">Кабинет</div><h1>Реквизиты</h1>
<div class="enote"><img src="logos/elena-photo.jpg">
<div class="et"><b>Подпись и печать автоматически подставляются в документы.</b> Загрузите один раз — и все документы будут подписаны 💛</div>
</div>
<!-- Подпись -->
<div style="margin-bottom:24px">
<h3 style="font-size:15px;font-weight:700;margin-bottom:4px">🖊️ Факсимиле (подпись)</h3>
<p style="font-size:13px;color:var(--mut);margin-bottom:12px">Сфотографируйте подпись на белой бумаге или загрузите скан. Мы автоматически уберём фон и сохраним.</p>
<div id="sig-preview" style="display:none;margin-bottom:12px">
<img id="sig-img" style="max-height:80px;border:1.5px solid var(--line);border-radius:8px;padding:8px;background:#fff">
<button class="svc-btn-detail" style="margin-left:8px;font-size:12px" onclick="_clearRequisite('sig')">✕ Удалить</button>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap">
<label style="cursor:pointer">
<input type="file" accept="image/*" style="display:none" onchange="_uploadRequisite('sig',this)">
<span class="btn btn-o" style="padding:8px 16px;font-size:13px">📷 Загрузить фото подписи</span>
</label>
<button class="svc-btn-detail" onclick="_drawSignature()" style="font-size:13px">✏️ Нарисовать подпись</button>
</div>
</div>
<hr style="border:none;border-top:1px solid var(--line);margin:20px 0">
<!-- Печать -->
<div style="margin-bottom:24px">
<h3 style="font-size:15px;font-weight:700;margin-bottom:4px">🔴 Печать / штамп</h3>
<p style="font-size:13px;color:var(--mut);margin-bottom:4px">Сфотографируйте печать на белой бумаге. Система уберёт фон и подберёт правильный размер.</p>
<p style="font-size:12px;color:var(--mut);margin-bottom:12px">Стандарт: круглая печать ⌀ 38-42 мм · треугольная 35×50 мм · прямоугольная для ИП 38×70 мм</p>
<div id="stamp-preview" style="display:none;margin-bottom:12px">
<img id="stamp-img" style="max-height:100px;border:1.5px solid var(--line);border-radius:8px;padding:8px;background:#fff">
<div id="stamp-size" style="font-size:12px;color:var(--mut);margin-top:4px"></div>
<button class="svc-btn-detail" style="margin-left:8px;font-size:12px" onclick="_clearRequisite('stamp')">✕ Удалить</button>
</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:8px">
<label style="cursor:pointer">
<input type="file" accept="image/*" style="display:none" onchange="_uploadRequisite('stamp',this)">
<span class="btn btn-o" style="padding:8px 16px;font-size:13px">📷 Загрузить фото печати</span>
</label>
<select id="stamp-type-sel" style="border:1.5px solid var(--line);border-radius:9px;padding:8px 12px;font-size:13px;font-family:inherit;color:var(--ink)">
<option value="round">Круглая (⌀ 40 мм)</option>
<option value="rect_ip">ИП прямоугольная (38×70 мм)</option>
<option value="rect_ooo">ООО прямоугольная (38×58 мм)</option>
<option value="triangle">Треугольная (35×50 мм)</option>
</select>
</div>
<p style="font-size:12px;color:var(--mut)">💡 Лайфхак: положите печать на белый лист, сфотографируйте при хорошем освещении без теней.</p>
</div>
<hr style="border:none;border-top:1px solid var(--line);margin:20px 0">
<!-- Реквизиты компании -->
<div>
<h3 style="font-size:15px;font-weight:700;margin-bottom:12px">🏢 Реквизиты для документов</h3>
<div style="display:flex;flex-direction:column;gap:10px;max-width:500px">
<input class="elena-main-inp" id="req-name" placeholder="Полное наименование (ООО «...» / ИП ФИО)" oninput="_saveRequisites()">
<input class="elena-main-inp" id="req-inn" placeholder="ИНН / ОГРН" oninput="_saveRequisites()">
<input class="elena-main-inp" id="req-addr" placeholder="Юридический адрес" oninput="_saveRequisites()">
<input class="elena-main-inp" id="req-phone" placeholder="Телефон" oninput="_saveRequisites()">
<input class="elena-main-inp" id="req-email" placeholder="Email" oninput="_saveRequisites()">
</div>
<p style="font-size:12px;color:var(--mut);margin-top:8px">Эти данные автоматически подставляются во все создаваемые документы.</p>
</div>
<!-- Canvas для рисования подписи -->
<div id="sig-draw-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1000;display:none;align-items:center;justify-content:center">
<div style="background:#fff;border-radius:16px;padding:20px;width:400px">
<div style="font-weight:700;margin-bottom:12px">✏️ Нарисуйте подпись</div>
<canvas id="sig-canvas" width="360" height="120" style="border:2px solid var(--line);border-radius:8px;cursor:crosshair;touch-action:none;background:#fff"></canvas>
<div style="display:flex;gap:10px;margin-top:12px">
<button class="btn btn-p" style="flex:1;padding:9px" onclick="_saveSigCanvas()">✅ Сохранить</button>
<button class="btn btn-o" style="padding:9px 14px" onclick="_clearCanvas()">🗑</button>
<button class="svc-btn-detail" onclick="document.getElementById('sig-draw-modal').style.display='none'">Отмена</button>
</div>
</div>
</div>
</div></div>
<!-- Составить документ -->
<div class="tabpane" id="p-create"><div class="main-body">
<div class="crumb">Кабинет</div><h1>Составить документ</h1>
<div class="enote" style="margin-bottom:20px"><img src="logos/elena-photo.jpg"><div class="et"><b>Составлю любой документ под ваши параметры.</b> Выберите тип, заполните ключевые данные — черновик будет готов за несколько секунд. 💛</div></div>
<!-- Шаги -->
<div class="create-steps">
<div class="cstep act" id="cstep-1"><div class="cstep-num">1</div>Тип документа</div>
<div class="cstep-arrow"></div>
<div class="cstep" id="cstep-2"><div class="cstep-num">2</div>Параметры</div>
<div class="cstep-arrow"></div>
<div class="cstep" id="cstep-3"><div class="cstep-num">3</div>Черновик готов</div>
</div>
<!-- Шаг 1: Тип документа -->
<div class="create-pane on" id="cp-type">
<div class="doc-type-intro">Выберите тип — Елена подберёт структуру и заполнит шаблон под ваши данные</div>
<div class="doc-type-grid">
<div class="doc-type-card sel" onclick="selectDocType('agent',this)">
<div class="dtc-ico">🤝</div>
<div class="dtc-name">Агентский договор</div>
<div class="dtc-desc">поручение + вознаграждение</div>
</div>
<div class="doc-type-card" onclick="selectDocType('trust',this)">
<div class="dtc-ico">📑</div>
<div class="dtc-name">Доверенность</div>
<div class="dtc-desc">полномочия представителя</div>
</div>
<div class="doc-type-card" onclick="selectDocType('claim',this)">
<div class="dtc-ico">✉️</div>
<div class="dtc-name">Претензия</div>
<div class="dtc-desc">оплата / неустойка</div>
</div>
<div class="doc-type-card" onclick="selectDocType('supply',this)">
<div class="dtc-ico">📦</div>
<div class="dtc-name">Договор поставки</div>
<div class="dtc-desc">товар + сроки + ответственность</div>
</div>
<div class="doc-type-card" onclick="selectDocType('rent',this)">
<div class="dtc-ico">🏠</div>
<div class="dtc-name">Аренда</div>
<div class="dtc-desc">помещение / оборудование</div>
</div>
<div class="doc-type-card" onclick="selectDocType('nda',this)">
<div class="dtc-ico">🔒</div>
<div class="dtc-name">NDA / Конфиденциальность</div>
<div class="dtc-desc">защита данных и ноу-хау</div>
</div>
<div class="doc-type-card" onclick="selectDocType('labor',this)">
<div class="dtc-ico">💼</div>
<div class="dtc-name">Трудовой договор</div>
<div class="dtc-desc">оформление сотрудника</div>
</div>
<div class="doc-type-card" onclick="selectDocType('dismiss',this)">
<div class="dtc-ico">🚪</div>
<div class="dtc-name">Расторжение</div>
<div class="dtc-desc">выйти без потерь</div>
</div>
</div>
<button class="doc-type-next show" id="doc-type-next" onclick="goCreateStep(2)">Заполнить параметры →</button>
</div>
<!-- Шаг 2: Параметры -->
<div class="create-pane" id="cp-form">
<div style="font-size:14px;font-weight:700;color:var(--ink);margin-bottom:16px" id="cf-doc-title">Агентский договор</div>
<div class="create-form-grid" id="create-form-fields">
<!-- Поля строятся через JS buildCreateForm() -->
<div class="cf-group"><label class="cf-label">Принципал (вы)</label><input class="cf-input" placeholder="ИП Васильев Руслан Геннадьевич"><span class="cf-hint">Полное наименование или ФИО</span></div>
<div class="cf-group"><label class="cf-label">Агент (контрагент)</label><input class="cf-input" placeholder="ООО «Зов Ресторанс»"><span class="cf-hint">Полное наименование</span></div>
<div class="cf-group wide"><label class="cf-label">Предмет поручения</label><input class="cf-input" placeholder="Поиск и привлечение клиентов для ресторана"><span class="cf-hint">Что именно делает агент</span></div>
<div class="cf-group"><label class="cf-label">Вознаграждение</label><input class="cf-input" placeholder="150 000 руб./мес."><span class="cf-hint">Сумма и периодичность</span></div>
<div class="cf-group"><label class="cf-label">Срок договора</label><input class="cf-input" placeholder="1 год с даты подписания"><span class="cf-hint">Начало и окончание</span></div>
<div class="cf-group"><label class="cf-label">Подсудность</label><input class="cf-input" placeholder="По месту нахождения Принципала"><span class="cf-hint">Суд при спорах</span></div>
<div class="cf-group"><label class="cf-label">НДС</label><input class="cf-input" placeholder="Не облагается — УСН"><span class="cf-hint">Или: в т.ч. НДС 20%</span></div>
</div>
<div class="enote" style="margin-bottom:16px;max-width:740px"><img src="logos/elena-photo.jpg"><div class="et">Заполните ключевые параметры — остальное (нумерацию, ссылки на нормы, форс-мажор, реквизиты) я добавлю автоматически 💛</div></div>
<div class="create-form-actions">
<button class="cf-back" onclick="goCreateStep(1)">← Назад</button>
<button class="cf-generate" onclick="goCreateStep(3)">⚡ Сгенерировать черновик</button>
</div>
</div>
<!-- Шаг 3: Черновик готов -->
<div class="create-pane" id="cp-preview">
<!-- Спиннер генерации -->
<div class="doc-generating" id="doc-generating">
<div class="dg-spinner"></div>
<div class="dg-text">Елена составляет черновик…</div>
<div class="dg-sub">Проверяю нормы ГК РФ · подбираю формулировки · займёт несколько секунд</div>
</div>
<!-- Превью документа -->
<div class="doc-preview-wrap" id="doc-preview-wrap">
<div class="doc-preview-actions">
<button class="dpa-btn dpa-dl" onclick="toast('⬇️ Скачиваю агентский договор в формате DOCX — файл будет готов через секунду')">⬇ Скачать DOCX</button>
<button class="dpa-btn dpa-copy" onclick="toast('📋 Текст договора скопирован в буфер обмена')">📋 Копировать</button>
<button class="dpa-btn dpa-add" onclick="toast('📁 Черновик добавлен в дело «Кухня — агентский (ЗОВ)»')"> В дело</button>
<button class="cf-back" style="border-radius:10px;padding:10px 16px;font-size:13px" onclick="goCreateStep(1)">← Новый</button>
</div>
<div class="doc-preview">
<h3>АГЕНТСКИЙ ДОГОВОР № <span class="doc-blank">___</span></h3>
<div class="doc-city">г. Краснодар &nbsp;&nbsp;&nbsp; <span class="doc-highlight">28 мая 2025 г.</span></div>
<div class="doc-parties">
<b>ПРИНЦИПАЛ:</b> <span class="doc-highlight">ИП Васильев Руслан Геннадьевич</span>, ОГРНИП <span class="doc-blank">____________</span>, ИНН <span class="doc-blank">____________</span>, именуемый в дальнейшем «Принципал»,<br><br>
<b>АГЕНТ:</b> <span class="doc-highlight">ООО «Зов Ресторанс»</span>, ИНН <span class="doc-blank">____________</span>, КПП <span class="doc-blank">____________</span>, именуемое в дальнейшем «Агент»,<br><br>
совместно именуемые «Стороны», заключили настоящий договор о нижеследующем:
</div>
<div class="doc-article">1. Предмет договора</div>
<div class="doc-clause">1.1. Принципал поручает, а Агент обязуется за вознаграждение совершать от имени и за счёт Принципала следующие действия: <span class="doc-highlight">поиск и привлечение клиентов для ресторана</span>.</div>
<div class="doc-clause">1.2. Агент действует в пределах полномочий, предоставленных настоящим договором и доверенностью (при необходимости).</div>
<div class="doc-article">2. Вознаграждение агента</div>
<div class="doc-clause">2.1. Вознаграждение Агента составляет <span class="doc-highlight">150 000 (сто пятьдесят тысяч) рублей</span> ежемесячно. <span class="doc-highlight">НДС не облагается — Агент применяет УСН.</span></div>
<div class="doc-clause">2.2. Изменение размера вознаграждения допускается исключительно по письменному соглашению Сторон.</div>
<div class="doc-clause">2.3. Оплата производится в течение 5 (пяти) рабочих дней с момента подписания акта за отчётный период.</div>
<div class="doc-article">3. Порядок исполнения</div>
<div class="doc-clause">3.1. Агент представляет отчёт не позднее <span class="doc-highlight">5-го числа</span> месяца, следующего за отчётным, с приложением первичных документов.</div>
<div class="doc-clause">3.2. Принципал вправе заявить возражения по отчёту в течение 3 (трёх) рабочих дней. По истечении данного срока отчёт считается принятым.</div>
<div class="doc-clause">3.3. Привлечение субагентов допускается исключительно с письменного согласия Принципала.</div>
<div class="doc-article">4. Ответственность сторон</div>
<div class="doc-clause">4.1. Ответственность Принципала ограничена суммой вознаграждения за 3 (три) последних месяца. Упущенная выгода возмещению не подлежит.</div>
<div class="doc-clause">4.2. За просрочку оплаты Принципал уплачивает пеню в размере 0,1% от суммы за каждый день просрочки.</div>
<div class="doc-article">5. Срок и расторжение</div>
<div class="doc-clause">5.1. Договор вступает в силу с даты подписания и действует <span class="doc-highlight">1 (один) год</span>.</div>
<div class="doc-clause">5.2. Каждая из Сторон вправе расторгнуть договор, уведомив другую сторону письменно не менее чем за 30 (тридцать) дней.</div>
<div class="doc-clause">5.3. Пролонгация допускается только при наличии письменного согласия обеих Сторон не позднее 30 дней до окончания срока.</div>
<div class="doc-article">6. Разрешение споров</div>
<div class="doc-clause">6.1. Все споры рассматриваются в арбитражном суде <span class="doc-highlight">по месту нахождения Принципала</span>.</div>
<div style="margin-top:24px;font-size:11px;color:var(--mut);border-top:1px solid #e5e7eb;padding-top:12px">
📌 Черновик сгенерирован ЗАЩИТА · проверен по нормам ГК РФ · внесите правки если нужно · данные хранятся на вашем устройстве
</div>
</div>
</div>
</div>
</div></div>
<!-- Баланс и оплата -->
<div class="tabpane" id="p-balance">
<div class="main-body">
<div class="crumb">Кабинет</div><h1>Баланс и оплата</h1>
<!-- Hero balance -->
<div class="bal-hero">
<div class="bal-hero-num"><span id="bal-credits-num">0</span></div>
<div class="bal-hero-lbl">кредитов на балансе</div>
<div class="bal-hero-sub" id="bal-sub-status">Подписка не активна</div>
</div>
<!-- Actions -->
<div class="bal-actions">
<div class="bal-action-btn" onclick="go('pay')"> Пополнить баланс</div>
<div class="bal-action-btn" id="bal-refund-toggle" onclick="toggleRefundForm()">↩ Запросить возврат</div>
</div>
<!-- Refund form -->
<div class="ref-form" id="ref-form" style="display:none">
<h4>Заявка на возврат средств</h4>
<div class="ref-form-hint">Возврат производится за неиспользованные кредиты в течение 10 рабочих дней согласно оферте.</div>
<textarea id="ref-reason" placeholder="Причина возврата (необязательно)..."></textarea>
<button class="btn" style="width:100%;background:var(--bg);color:#fff;border:none;padding:12px;border-radius:10px;font-weight:700;cursor:pointer" onclick="submitRefund()">Отправить заявку</button>
</div>
<!-- History -->
<div class="admin-section" style="margin-top:8px">
<div class="admin-section-hdr">
<div class="admin-section-ttl">История платежей</div>
</div>
<div id="bal-history"></div>
</div>
</div>
</div>
</main>
</div>
</section>
<script>
/* ── СТАТИСТИКА ── */
function statTrack(mode, custom) {
const key = 'zashita_intake_stats';
const stats = JSON.parse(localStorage.getItem(key) || '[]');
stats.push({ mode, custom: custom||null, ts: new Date().toISOString() });
localStorage.setItem(key, JSON.stringify(stats));
renderStats();
}
function statGet() {
const raw = JSON.parse(localStorage.getItem('zashita_intake_stats') || '[]');
const counts = { novice:0, mid:0, pro:0, custom:0 };
raw.forEach(r => { if(counts[r.mode]!==undefined) counts[r.mode]++; else counts.custom++; });
return { counts, total: raw.length, customs: raw.filter(r=>r.mode==='custom').map(r=>r.custom) };
}
function renderStats() {
let el = document.getElementById('stats-pill');
if (!el) { el = document.createElement('div'); el.id='stats-pill'; el.className='stats-pill'; el.title='Нажать — очистить'; el.onclick=()=>{localStorage.removeItem('zashita_intake_stats');renderStats();}; document.body.appendChild(el); }
const s = statGet();
if (!s.total) { el.style.display='none'; return; }
el.style.display='block';
el.innerHTML = `<b>Intake-статистика</b> (${s.total})<br>`
+ `🙋 Новичок: ${s.counts.novice} · 📖 Средний: ${s.counts.mid}<br>`
+ `⚖️ Юрист: ${s.counts.pro} · ✏️ Другое: ${s.counts.custom}`
+ (s.customs.length ? `<br><span style="color:rgba(255,255,255,.5);font-size:10px">${s.customs.slice(-2).join(' / ')}</span>` : '');
}
/* ── КОНТЕНТ ПО РЕЖИМУ ── */
const MODES = {
novice: {
badge: '🙋 Режим: объясняем с нуля',
badgeCls: 'mode-novice',
ack: 'Хорошо, что сказали 💛 Буду рядом — объясню всё простым языком, без страшных слов. Если что-то непонятно — просто спросите, не стесняйтесь.',
introTail: 'Не пугайтесь —',
r1q: '«Агент несёт полную ответственность за качество выполненных работ перед конечным потребителем»',
r1: 'Смотрите: это значит, что если у клиента сломается мебель или он пожалуется — по этому пункту отвечаете <b>вы лично</b>, а не компания-заказчик. То есть возвраты и штрафы могут прийти на ваш карман. Это типовая ловушка — не переживайте, мы её аккуратно перепишем.',
r2q: '«Агент обязуется возместить Принципалу все финансовые потери, возникшие вследствие ненадлежащего исполнения обязательств»',
r2: 'О чём это говорит: вы соглашаетесь покрыть «все финансовые потери компании» — и <b>без верхней границы суммы</b>. По сути это открытый кошелёк. Звучит пугающе, понимаю — но от этого защититься просто, я покажу как.',
r3q: '«Агент выполняет работы лично, в установленные часы, на оборудовании Принципала, по его инструкциям»',
r3: 'Это значит что: договор назван агентским, но по описанию работы — он похож на трудовой. Налоговая вправе это заметить и <b>доначислить НДФЛ и взносы задним числом</b>. Звучит тревожно — но от этого реально защититься.',
lock: '🔒 Это только 3 момента из 12. Есть ещё — и там тоже важное. В полном разборе я объясню каждый пункт простым языком, скажу что именно нужно изменить и почему другая сторона обязана на это согласиться. <b>Вы уйдёте с чётким пониманием что подписываете — и уверенностью, что вас защитили</b> 💛',
forkQ: 'Чего бы вам хотелось? Объясню каждый путь — выберем вместе:'
},
mid: {
badge: '📖 Режим: по делу, без лишнего',
badgeCls: 'mode-mid',
ack: 'Принято. Работаем по делу — цитата из договора, риск, что делать. Без воды.',
introTail: 'Фиксируем критичные.',
r1q: '«Агент несёт полную ответственность за качество выполненных работ перед конечным потребителем»',
r1: '<b>Риск:</b> ответственность перед конечным потребителем переложена на вас. По ЗоЗПП ст. 429 — это влечёт прямые претензии и штрафы в ваш адрес, минуя Принципала. <b>Решение:</b> ограничить ответственность агента пределами вознаграждения.',
r2q: '«Агент обязуется возместить Принципалу все финансовые потери, возникшие вследствие ненадлежащего исполнения»',
r2: '<b>Риск:</b> неограниченная финансовая ответственность — ст. 330 ГК не требует устанавливать потолок, но без него сумма ничем не ограничена. <b>Решение:</b> добавить cap = сумма вознаграждения по договору.',
r3q: '«Агент выполняет работы лично, в установленные часы, на оборудовании Принципала, по его инструкциям»',
r3: '<b>Риск:</b> признаки трудовых отношений по ст. 19.1 ТК. При налоговой проверке — переквалификация и доначисление НДФЛ + взносов. <b>Решение:</b> переформулировать признаки самостоятельности.',
lock: '🔒 Ещё 9 рисков, из них 2 критичных — в полном заключении с нормами и готовыми формулировками.',
forkQ: 'Как будем действовать?'
},
pro: {
badge: '⚖️ Профессиональный режим',
badgeCls: 'mode-pro',
ack: 'Принял. Цитата → норма → квалификация → рекомендация. Поехали.',
introTail: 'Выявлено 5 критических.',
r1q: '«Агент несёт полную ответственность за качество выполненных работ перед конечным потребителем»',
r1: '<b>Квалификация:</b> прямое возложение ответственности исполнителя перед третьим лицом (потребителем) в нарушение ст. 4, 18, 29 ЗоЗПП — ответственность принципала не исключается, а дублируется. <b>Рекомендация:</b> п.1.1 изложить в редакции: «Агент несёт ответственность перед Принципалом в пределах суммы агентского вознаграждения».',
r2q: '«Агент обязуется возместить Принципалу все финансовые потери, возникшие вследствие ненадлежащего исполнения»',
r2: '<b>Квалификация:</b> неограниченная договорная ответственность, ст. 330333 ГК РФ. Судебная практика: без cap суд снижает по ст. 333 ГК, но только по заявлению. <b>Рекомендация:</b> установить cap = агентское вознаграждение × 2.',
r3q: '«Агент выполняет работы лично, в установленные часы, на оборудовании Принципала, по его инструкциям»',
r3: '<b>Квалификация:</b> признаки трудовых отношений по ст. 15, 19.1 ТК РФ: личное исполнение, режим рабочего времени, инструктаж, материально-техническое обеспечение. Риск: переквалификация + ст. 122 НК РФ (недоимка + пени + штраф 20%). <b>Рекомендация:</b> исключить нормы п.п. о личном исполнении и режиме.',
lock: '📋 Полное заключение: 12 рисков, нормативная база, альтернативные формулировки, протокол разногласий.',
forkQ: 'Стратегия:'
}
};
function setMode(mode) {
const custom = mode === 'custom' ? (document.getElementById('intake-custom').value.trim() || 'не указано') : null;
if (mode === 'custom' && !custom) { document.getElementById('intake-custom').focus(); return; }
statTrack(mode, custom);
const cfg = MODES[mode] || MODES.mid;
// badge
const badge = document.getElementById('elena-mode-badge');
badge.innerHTML = `<span class="mode-badge ${cfg.badgeCls}">${cfg.badge}</span>`;
// step2 content
document.getElementById('el-ack').innerHTML = custom
? `Понял: «${custom}». Подстроюсь под вас — буду объяснять по ходу.`
: cfg.ack;
document.getElementById('el-intro-tail').textContent = cfg.introTail;
document.getElementById('r1-quote').textContent = cfg.r1q;
document.getElementById('r1-text').innerHTML = cfg.r1;
document.getElementById('r2-quote').textContent = cfg.r2q;
document.getElementById('r2-text').innerHTML = cfg.r2;
document.getElementById('r3-quote').textContent = cfg.r3q;
document.getElementById('r3-text').innerHTML = cfg.r3;
document.getElementById('el-lock').innerHTML = cfg.lock;
document.getElementById('el-fork-q').textContent = cfg.forkQ;
// show upload step
document.getElementById('el-step1').style.display = 'none';
document.getElementById('el-step-upload').style.display = 'block';
window.scrollTo(0, 0);
}
/* ── ТИПЫ ДОГОВОРОВ ── */
const CTYPES = {
agent: {
emoji:'🤝', name:'агентский договор',
comment:'Агент действует от имени или за счёт другой стороны. Главные риски здесь — объём ответственности перед третьими лицами и признаки трудовых отношений.',
delivRec:'partner',
intro:'С учётом выявленных нарушений потенциальный ущерб — от <b>120 000 до 350 000 ₽</b>: налоговые доначисления, штрафы от потребителей, неограниченная ответственность перед Принципалом. Три варианта:',
delivDesc:{
protocol:'Список требований с нормой закона под каждым. Не «хочу изменить», а «данная редакция противоречит ст. 330 ГК». Контрагенту придётся либо согласиться, либо объяснить почему закон неважен.',
redact:'Новая редакция каждого спорного пункта + объяснение логики изменений. Контрагент видит аргументы, а не давление — меньше возражений, быстрее к подписи.',
clean:'Все спорные пункты переписаны и готовы к подписанию — без пояснений для контрагента. Подходит если другая сторона уже согласна на правки.',
partner:'Договор написан так, чтобы обе стороны видели свою выгоду. Контрагент сам не захочет его менять — его интересы уже учтены. Подписывают без торга 💛'
}
},
realty: {
emoji:'🏠', name:'договор купли-продажи недвижимости',
comment:'В недвижимости цена ошибки особенно высока. Проверяю обременения, условия передачи объекта и ответственность за скрытые дефекты.',
delivRec:'partner',
intro:'В сделках с недвижимостью любая неточность стоит дорого — вплоть до <b>потери аванса или расторжения сделки</b>. Три варианта:',
delivDesc:{
protocol:'Фиксируем спорные условия по передаче, дефектам и срокам — со ссылками. Основа для предметного разговора. Хорошо работает с частными продавцами.',
redact:'Переписываем проблемные пункты с объяснением зачем. Особенно эффективно с застройщиком — они реагируют на аргументы, а не на просьбы.',
clean:'Договор полностью переписан и готов к подписанию. Все риски закрыты, формулировки чёткие.',
partner:'Договор сбалансирован: другая сторона видит, что её интересы учтены. Сделки закрываются быстрее и без претензий после регистрации 💛'
}
},
auto: {
emoji:'🚗', name:'договор купли-продажи автомобиля',
comment:'Смотрю на гарантии состояния, скрытые дефекты и условия передачи.',
delivRec:'clean',
intro:'Главные риски — <b>скрытые дефекты без гарантии возврата</b>. Потенциальные потери: от стоимости ремонта до полной цены автомобиля. Три пути:',
delivDesc:{
protocol:'Гарантии состояния, ответственность за скрытые дефекты, порядок расторжения — со ссылками. Основа для переговоров до подписания.',
redact:'Новая редакция с объяснением зачем. Продавец понимает логику — реже отказывает. Подходит если продавец настроен на диалог.',
clean:'Договор полностью готов к подписанию — скрытые дефекты закрыты, условия передачи чёткие. Приходите с текстом, а не со списком претензий 💛',
partner:'Вариант, где продавец тоже видит свою защиту. Снижает напряжение — подписывают быстро и без претензий.'
}
},
construction: {
emoji:'🛋️', name:'договор подряда',
comment:'Договор подряда — здесь критичны сроки, критерии качества результата и ответственность за задержки.',
delivRec:'redact',
intro:'Основные риски — <b>штрафы за просрочку без верхней границы</b> и размытые критерии качества. Потенциальные потери: от устранения недостатков до полной переделки. Три варианта:',
delivDesc:{
protocol:'Требования к срокам, критериям приёмки и порядку устранения дефектов — с нормами. Заказчику сложнее отказать без обоснования.',
redact:'Переписываем спорные пункты, объясняем зачем. Обе стороны понимают что подписывают — меньше споров при сдаче. Оптимальный выбор для подряда 💛',
clean:'Договор переписан и готов к подписанию. Сроки зафиксированы, критерии приёмки чёткие, ответственность ограничена.',
partner:'Договор учитывает риски заказчика и подрядчика. Заказчик видит защиту своих интересов — соглашается быстрее.'
}
},
services: {
emoji:'🎯', name:'договор оказания услуг',
comment:'В услугах часто размыт сам результат и ответственность исполнителя.',
delivRec:'redact',
intro:'Главные риски — <b>размытый результат и ответственность за то, что не зависит от вас</b>. Потенциальные потери: от возврата всей суммы до штрафных санкций. Три пути:',
delivDesc:{
protocol:'Чёткие требования к тому, что считается результатом, срокам и основаниям для расторжения. Снимает частые точки споров до старта работ.',
redact:'Новая редакция с объяснением логики. Заказчик понимает условия — меньше конфликтов при сдаче. Лучший выбор для услуг 💛',
clean:'Договор полностью готов к подписанию — результат зафиксирован, критерии приёмки прописаны, ответственность ограничена.',
partner:'Договор балансирует интересы исполнителя и заказчика. Работают без конфликтов и платят без задержек.'
}
},
labor: {
emoji:'📋', name:'трудовой договор',
comment:'Трудовой договор — смотрю на режим работы, зоны ответственности и условия расторжения.',
delivRec:'consult',
intro:'Потенциальные потери — <b>от нескольких окладов до судебного восстановления</b>. Работодатель может переложить ответственность сверх нормы ТК. Три варианта:',
delivDesc:{
protocol:'Пункты, противоречащие ТК, с нормами. Основа для переговоров с HR: работодатель видит закон, а не просьбу. Большинство соглашаются на правки 💛',
redact:'Новые формулировки спорных условий с объяснением. Работодатель понимает запрос — проще найти компромисс.',
clean:'Договор приведён в соответствие с ТК и готов к подписанию. Ваши права зафиксированы.',
partner:'Договор учитывает интересы работника и работодателя. Подписывают без споров и работают без неожиданных претензий.'
}
},
loan: {
emoji:'💰', name:'договор займа',
comment:'Ключевые риски займа — скрытые проценты, штрафные санкции и условия досрочного требования.',
delivRec:'redact',
intro:'При наихудшем сценарии <b>задолженность вырастет в несколько раз</b> от первоначальной суммы. Скрытые проценты и неограниченные штрафы — основной инструмент давления. Три пути:',
delivDesc:{
protocol:'Требования по процентам, порядку штрафов и условиям погашения — со ссылками. Кредитору сложнее отказать без объяснений.',
redact:'Новая редакция ключевых пунктов с объяснением — снимает инструменты давления на заёмщика. Оптимальный выбор для займов с физлицами и МФО 💛',
clean:'Договор готов к подписанию. Проценты зафиксированы, штрафы ограничены, условия прозрачны.',
partner:'Договор сбалансирован: кредитор защищён, заёмщик знает точные условия без скрытых ловушек.'
}
},
supply: {
emoji:'📦', name:'договор поставки',
comment:'Поставка — проверяю условия приёмки, ответственность за качество и что происходит при просрочке.',
delivRec:'reply',
intro:'Основные риски — <b>неограниченная неустойка за просрочку и ответственность за качество без потолка</b>. Потенциальные потери: от стоимости партии до срыва цепочки поставок. Три варианта:',
delivDesc:{
protocol:'Требования по приёмке, критериям качества и ограничению неустойки — со ссылками. Контрагент видит закон, а не просьбу.',
redact:'Новая редакция спорных условий с объяснением. Обе стороны знают правила до старта — меньше споров при исполнении 💛',
clean:'Договор готов к подписанию. Порядок приёмки чёткий, неустойка ограничена, ответственность зафиксирована.',
partner:'Договор учитывает интересы поставщика и покупателя. Подписывают быстро и работают без задержек.'
}
},
other: {
emoji:'📄', name:'договор',
comment:'Изучила структуру. Нашла пункты, которые стоит проверить внимательнее.',
delivRec:'consult',
intro:'Формальные нарушения дешевле устранить <b>до подписания</b>, чем оспаривать потом. Три варианта:',
delivDesc:{
protocol:'Список требований с правовым обоснованием. Основа для переговоров если знаете что именно изменить.',
redact:'Новая редакция спорных пунктов с объяснением. Контрагент понимает зачем — меньше возражений.',
clean:'Договор полностью готов к подписанию. Риски закрыты, формулировки чёткие 💛',
partner:'Договор учитывает интересы обеих сторон. Подписывают без торга и выполняют без претензий.'
}
}
};
const CTYPES_KEYWORDS = {
agent: ['агент','принципал','агентск','от имени и за счёт','за счёт принципала'],
realty: ['квартир','недвижим','жиль','комнат','нежилое','земельн','ипотек','объект недвижим','жилой дом'],
auto: ['автомобил','транспортн средств','машин','птс','пробег','кузов'],
construction: ['подряд','мебел','изготовл','монтаж','строител','ремонт','подрядчик','заказчик','результат работ'],
services: ['оказани','услуг','исполнител','заказчик','сервис','консульт','разработ','дизайн'],
labor: ['трудов','работодател','работник','должност','оклад','отпуск','увольнен','рабочее время'],
loan: ['займ','заём','кредит','процент годов','долг','заемщик','заёмщик','сумма займа'],
supply: ['поставщик','покупател','товар','партия','поставка','отгрузк','накладн']
};
let _demoCtypeIdx = 0;
const _demoSeq = ['agent','realty','auto','construction','services','labor','loan','supply'];
function detectCtype(text) {
if (text && text.length > 30) {
const t = text.toLowerCase();
for (const [key, kws] of Object.entries(CTYPES_KEYWORDS)) {
if (kws.some(kw => t.includes(kw))) return key;
}
return 'other';
}
// демо-режим: каждый клик — новый тип
const key = _demoSeq[_demoCtypeIdx % _demoSeq.length];
_demoCtypeIdx++;
return key;
}
function showResults(ctypeKey) {
_curCtypeKey = ctypeKey || 'other';
// спрятать предыдущие шаги на случай прямого вызова
['el-step-upload','el-step-scan','el-step1'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.display = 'none';
});
const ctype = CTYPES[ctypeKey] || CTYPES.other;
// тип договора в первой реплике
document.getElementById('el-scan-type').textContent = ctype.emoji + ' ' + ctype.name;
const noteEl = document.getElementById('el-ctype-note');
noteEl.textContent = ctype.comment;
noteEl.style.display = ctype.comment ? 'block' : 'none';
// убрать старую подсветку, поставить новую
document.querySelectorAll('.deliv').forEach(el => {
el.classList.remove('deliv-highlighted');
});
// вступление Елены (оценка ущерба)
const pitchEl = document.getElementById('el-ctype-pitch');
const pitchMsg = document.getElementById('el-pitch-msg');
if (ctype.intro) {
pitchEl.innerHTML = ctype.intro;
pitchMsg.style.display = '';
} else {
pitchMsg.style.display = 'none';
}
// описания на карточках
if (ctype.delivDesc) {
['protocol','redact','clean','partner'].forEach(key => {
const el = document.getElementById('dd2-' + key);
if (el && ctype.delivDesc[key]) el.textContent = ctype.delivDesc[key];
});
}
// убрать badge у всех, потом поставить на нужный
document.querySelectorAll('.deliv-badge').forEach(b => b.style.display = 'none');
if (ctype.delivRec) {
const recEl = document.getElementById('deliv-' + ctype.delivRec);
if (recEl) {
recEl.classList.add('deliv-highlighted');
let badge = recEl.querySelector('.deliv-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'deliv-badge';
recEl.appendChild(badge);
}
badge.textContent = 'Рекомендуем';
badge.style.display = '';
}
}
// показать результаты
document.getElementById('el-step-scan').style.display = 'none';
document.getElementById('el-step2').style.display = 'block';
document.getElementById('el-actbar').style.display = 'flex';
document.getElementById('el-step2').scrollIntoView({ behavior: 'smooth' });
// Проверяем есть ли связанный договор для сравнения
_checkForContractLink(text, ctypeKey);
}
// ── СВЯЗКА ДОКУМЕНТОВ + ПОИСК ПРОТИВОРЕЧИЙ ─────────────────────────────────
function _checkForContractLink(newDocText, newDocType) {
/* Если это акт/доп.соглашение — проверяем есть ли договор в хранилище */
var actTypes = ['акт', 'act', 'дополнительное соглашение', 'допсоглашение'];
var isAct = actTypes.some(function(t){ return (newDocType||'').toLowerCase().includes(t); });
if (!isAct) return;
var contracts = _getContracts();
if (!contracts || !contracts.length) return;
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
// Показываем предложение сравнить с договором
var old = document.getElementById('compare-offer'); if (old) return;
var div = document.createElement('div');
div.id = 'compare-offer';
div.className = 'msg';
div.innerHTML =
'<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'Вижу что это акт. В вашем кабинете есть ' + contracts.length + ' договор(а). ' +
'Сравнить с ним — могу найти противоречия до подписания.' +
'<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">' +
contracts.slice(0,3).map(function(c, i){
return '<button class="svc-btn-detail" style="font-size:12px" onclick="_compareWithContract(' + i + ')">' +
'📄 ' + (c.type || 'Договор') + (c.counterparty ? ' · ' + c.counterparty.slice(0,15) : '') +
'</button>';
}).join('') +
'<button class="svc-btn-detail" style="color:var(--mut);font-size:12px" onclick="this.closest(\'.msg\').remove()">Пропустить</button>' +
'</div></div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
function _compareWithContract(contractIdx) {
var contracts = _getContracts();
var contract = contracts[contractIdx];
if (!contract) return;
var actText = (document.getElementById('el-paste') || {}).value || '';
var contractText = contract.preview || '';
if (!contractText || contractText.length < 50) {
toast('⚠️ Текст договора не сохранён — загрузите договор заново для полного сравнения');
return;
}
var offer = document.getElementById('compare-offer'); if (offer) offer.remove();
var wrap = document.querySelector('.chatwrap');
// Progress bubble
var prog = document.createElement('div');
prog.id = 'compare-progress';
prog.className = 'msg';
prog.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'🔍 Сравниваю документы… <span id="cmp-dots">.</span></div></div>';
if (wrap) wrap.appendChild(prog);
prog.scrollIntoView({behavior:'smooth'});
var dots = 0;
var dotsInterval = setInterval(function(){
var el = document.getElementById('cmp-dots');
if (el) el.textContent = ['.','..','.'][dots++ % 3];
}, 500);
fetch(API_BASE + '/api/compare', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
contract_text: contractText,
document_text: actText,
doc_type: 'Акт приёмки-передачи'
})
})
.then(function(r){ return r.json(); })
.then(function(data){
clearInterval(dotsInterval);
var p = document.getElementById('compare-progress'); if (p) p.remove();
_showCompareResults(data, contract);
})
.catch(function(e){
clearInterval(dotsInterval);
var p = document.getElementById('compare-progress'); if (p) p.remove();
toast('Ошибка сравнения: ' + e.message);
});
}
function _showCompareResults(data, contract) {
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
var contradictions = data.contradictions || [];
var verdict = data.verdict || 'safe';
var verdictColors = {safe:'#16a34a', review:'#d97706', danger:'#dc2626'};
var verdictLabels = {safe:'✅ Противоречий нет', review:'⚠️ Есть расхождения', danger:'🚨 Критические противоречия'};
var verdictColor = verdictColors[verdict] || '#374151';
var html = '<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'<div style="font-weight:700;color:' + verdictColor + ';margin-bottom:8px">' +
verdictLabels[verdict] + '</div>';
if (data.summary) {
html += '<div style="font-size:13px;margin-bottom:10px">' + data.summary + '</div>';
}
if (contradictions.length) {
html += '<div style="display:flex;flex-direction:column;gap:8px">';
contradictions.forEach(function(c) {
var riskColor = {critical:'#dc2626', high:'#d97706', medium:'#2563eb'}[c.risk] || '#374151';
html += '<div style="border:1.5px solid ' + riskColor + ';border-radius:10px;padding:10px;background:#fafafa">' +
'<div style="font-weight:700;font-size:13px;color:' + riskColor + '">' +
({critical:'🔴', high:'🟠', medium:'🟡'}[c.risk] || '⚪') + ' ' + (c.field || '') + '</div>' +
'<div style="font-size:12px;color:#6b7280;margin:4px 0">' +
'<b>Договор:</b> ' + (c.in_contract || '') + '<br>' +
'<b>Акт:</b> ' + (c.in_document || '') +
'</div>' +
'<div style="font-size:12px;background:#fef2f2;border-radius:6px;padding:6px;margin-top:4px">' +
'⚠️ ' + (c.consequence || '') +
'</div>' +
'<div style="font-size:12px;color:#16a34a;margin-top:4px">✅ ' + (c.recommendation || '') + '</div>' +
'</div>';
});
html += '</div>';
}
// Кнопки действий
html += '<div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap">';
if (verdict === 'danger' || verdict === 'review') {
html += '<button class="btn btn-p" style="padding:7px 14px;font-size:12px" onclick="_offerPartner(\'Противоречия в акте\')">👨‍⚖️ Нужен юрист-партнёр</button>';
}
html += '<button class="svc-btn-detail" style="font-size:12px" onclick="this.closest(\'.bubble\').querySelectorAll(\'[style*=flex]\').forEach(function(e){e.remove()})">Закрыть</button>';
html += '</div>';
html += '</div></div>';
var div = document.createElement('div');
div.innerHTML = html;
wrap.appendChild(div.firstChild || div);
wrap.lastChild.scrollIntoView({behavior:'smooth'});
// Обновляем досье
if (contradictions.length) {
_updateDossier({
facts: ['Найдены противоречия в акте с договором: ' + contradictions.map(function(c){return c.field;}).join(', ')],
open: ['Решить противоречия перед подписанием акта']
});
}
}
// ── МОДЕЛЬ ПАРТНЁРОВ ─────────────────────────────────────────────────────────
function _offerPartner(reason) {
var wrap = document.querySelector('.chatwrap') || document.getElementById('rchat-msgs');
if (!wrap) return;
var old = document.getElementById('partner-offer'); if (old) { old.scrollIntoView({behavior:'smooth'}); return; }
var div = document.createElement('div');
div.id = 'partner-offer';
div.className = 'msg';
var ctx = _buildElenaContext();
div.innerHTML =
'<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'<div style="font-weight:700;margin-bottom:6px">👨‍⚖️ Подключить юриста-партнёра</div>' +
'<div style="font-size:13px;color:#374151;margin-bottom:10px">' +
'Я подготовила досье по вашему делу. Юрист-партнёр получит полный контекст ' +
'и свяжется с вами в течение нескольких часов — уже подготовленным.' +
'</div>' +
'<div style="font-size:12px;color:#6b7280;margin-bottom:10px">' +
'Повод обращения: <b>' + (reason || 'сложный кейс') + '</b>' +
'</div>' +
'<div style="display:flex;flex-direction:column;gap:8px">' +
'<input id="partner-phone" type="tel" placeholder="Ваш телефон для связи" ' +
'style="border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none">' +
'<div style="display:flex;gap:8px">' +
'<button class="btn btn-p" style="flex:1;padding:9px;font-size:13px" onclick="_submitPartnerRequest(\'' + (reason||'') + '\')">📨 Отправить заявку</button>' +
'<button class="svc-btn-detail" onclick="document.getElementById(\'partner-offer\').remove()">Отмена</button>' +
'</div>' +
'</div></div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
setTimeout(function(){ var i = document.getElementById('partner-phone'); if (i) i.focus(); }, 300);
}
function _submitPartnerRequest(reason) {
var phone = (document.getElementById('partner-phone') || {}).value || '';
var ctx = _buildElenaContext();
var dossier = _getDossier() || {};
var history = _chatHistory.slice(-10);
// Собираем краткое описание кейса
var caseSummary = reason || 'Клиент запросил помощь юриста';
if (ctx.case_context) caseSummary += '\n\nКонтекст: ' + ctx.case_context.slice(0, 500);
if (history.length) {
caseSummary += '\n\nПоследнее из диалога: ' +
history.slice(-4).map(function(m){ return m.role + ': ' + m.content; }).join('\n');
}
var offer = document.getElementById('partner-offer'); if (offer) offer.remove();
toast('⏳ Отправляю заявку…');
fetch(API_BASE + '/api/partner', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
client_name: ctx.client_name || 'Клиент',
phone: phone,
case_summary: caseSummary,
dossier: dossier,
complexity_score: 3
})
})
.then(function(r){ return r.json(); })
.then(function(data){
var wrap = document.querySelector('.chatwrap') || document.getElementById('rchat-msgs');
if (!wrap) return;
var div = document.createElement('div');
div.className = 'msg';
div.innerHTML =
'<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'<div style="color:#16a34a;font-weight:700;margin-bottom:6px">✅ Заявка принята</div>' +
'<div style="font-size:13px">' + (data.message || '') + '</div>' +
'<div style="font-size:11px;color:#9ca3af;margin-top:6px">Номер заявки: <b>' + (data.ticket_id || '') + '</b></div>' +
'</div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
_updateDossier({ decisions: ['Передано юристу-партнёру: ' + (data.ticket_id || '')] });
})
.catch(function(e){ toast('Ошибка: ' + e.message); });
}
/* ── СКАНИРОВАНИЕ ── */
const SCAN_PHRASES = [
'Читаю договор...',
'Проверяю условия сторон...',
'Ищу скрытые риски...',
'Анализирую ответственность...',
'Оцениваю сроки и санкции...',
'Формирую заключение...'
];
function startScan() {
const text = (document.getElementById('el-paste').value || '').trim();
const ctypeKey = detectCtype(text);
document.getElementById('el-step-upload').style.display = 'none';
document.getElementById('el-step-scan').style.display = 'block';
window.scrollTo(0, 0);
let i = 0;
const lbl = document.getElementById('scan-label');
const interval = setInterval(() => {
i = (i + 1) % SCAN_PHRASES.length;
lbl.style.opacity = '0';
setTimeout(() => { lbl.textContent = SCAN_PHRASES[i]; lbl.style.opacity = '1'; }, 180);
}, 800);
// Параллельно запускаем реальный API (если доступен)
var _apiResult = null;
if (_apiAvailable && text) {
fetch(API_BASE + '/api/deadlines', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({text: text})
})
.then(function(r){ return r.json(); })
.then(function(data){
_apiResult = data;
// Сохраняем сроки для контекста Елены
if (data.deadlines && data.deadlines.length) {
_contractDeadlines = data.deadlines;
// Обновляем _DEADLINES для экрана "Сроки"
_DEADLINES = data.deadlines.map(function(d, idx){
return {
id: idx + 100,
caseId: 'case-new',
caseName: (data.meta && data.meta.type) || 'Договор',
title: d.title,
type: d.type || 'Другое',
date: d.date,
quote: d.quote || '',
done: false
};
});
// Сохраняем в localStorage — переживёт перезагрузку страницы
try { localStorage.setItem('zashita_deadlines', JSON.stringify(_DEADLINES)); } catch(e){}
// Сохраняем договор в хранилище (persistent memory)
_saveContract(data.meta, data.risks, data.deadlines, text);
}
})
.catch(function(e){ console.warn('API /deadlines:', e); });
}
setTimeout(() => {
clearInterval(interval);
lbl.style.opacity = '0';
setTimeout(() => {
showResults(ctypeKey);
// Если API вернул need_signed_date — Елена спрашивает дату
if (_apiResult && _apiResult.need_signed_date) {
_askSignedDate();
}
// Если complexity_score >= 3 — предлагаем Council
if (_apiResult && _apiResult.council_trigger) {
setTimeout(function(){ _offerCouncil(_apiResult); }, 1500);
}
}, 200);
}, 4000);
}
function _askSignedDate() {
/* Елена спрашивает дату подписания после сканирования */
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
var div = document.createElement('div');
div.id = 'signed-date-ask';
div.innerHTML =
'<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'Договор уже подписан? Укажите дату — буду считать живые сроки и покажу что горит прямо сейчас.</div></div>' +
'<div style="display:flex;gap:8px;margin:8px 0 16px 48px;flex-wrap:wrap">' +
'<button class="btn btn-o" style="padding:7px 14px;font-size:13px" onclick="_setSignedDate(\'today\')">Сегодня</button>' +
'<button class="btn btn-o" style="padding:7px 14px;font-size:13px" onclick="_setSignedDate(\'yesterday\')">Вчера</button>' +
'<input type="date" id="signed-date-inp" style="border:1.5px solid var(--line);border-radius:9px;padding:7px 10px;font-size:13px;font-family:inherit">' +
'<button class="btn btn-p" style="padding:7px 14px;font-size:13px" onclick="_setSignedDate(\'input\')">Указать</button>' +
'</div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
function _setSignedDate(mode) {
var today = new Date();
var d;
if (mode === 'today') {
d = today.toISOString().slice(0,10);
} else if (mode === 'yesterday') {
today.setDate(today.getDate() - 1);
d = today.toISOString().slice(0,10);
} else {
d = (document.getElementById('signed-date-inp') || {}).value;
if (!d) { toast('Выберите дату'); return; }
}
// Убираем вопрос
var ask = document.getElementById('signed-date-ask');
if (ask) ask.remove();
// Перезапрашиваем deadlines с датой
var text = (document.getElementById('el-paste').value || '').trim();
if (!text || !_apiAvailable) { toast('📅 Дата ' + d + ' сохранена'); return; }
fetch(API_BASE + '/api/deadlines', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({text: text, signed_date: d})
})
.then(function(r){ return r.json(); })
.then(function(data){
if (data.deadlines) {
_contractDeadlines = data.deadlines;
_DEADLINES = data.deadlines.map(function(dl, idx){
return {id:idx+100, caseId:'case-new', caseName:(data.meta&&data.meta.type)||'Договор',
title:dl.title, type:dl.type||'Другое', date:dl.date, quote:dl.quote||'', done:false};
});
try { localStorage.setItem('zashita_deadlines', JSON.stringify(_DEADLINES)); } catch(e){}
// Показываем что горит
var hot = data.deadlines.filter(function(dl){ return dl.status === 'overdue' || dl.status === 'critical'; });
if (hot.length) {
_showHotDeadlines(hot);
} else {
toast('📅 Сроки пересчитаны на ' + d);
}
if (data.council_trigger) setTimeout(function(){ _offerCouncil(data); }, 1000);
}
})
.catch(function(){ toast('📅 Дата сохранена'); });
}
function _showHotDeadlines(hot) {
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
var list = hot.map(function(d){
return '<div style="padding:8px 0;border-bottom:1px solid #f3f4f6">' +
'<b>' + d.title + '</b> — <span style="color:#9f1239">' + (d.status_label || d.date) + '</span>' +
(d.quote ? '<div style="font-size:12px;color:#6b7280;margin-top:3px">' + d.quote + '</div>' : '') +
'</div>';
}).join('');
var div = document.createElement('div');
div.innerHTML =
'<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'🔴 Обратите внимание — есть горящие сроки:' +
'<div style="margin-top:10px">' + list + '</div>' +
'</div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
function _offerCouncil(data) {
/* Предлагаем Council для сложных кейсов */
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
var old = document.getElementById('council-offer'); if(old) return; // уже показано
var div = document.createElement('div');
div.id = 'council-offer';
div.innerHTML =
'<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'Ситуация требует глубокого анализа — я вижу признаки сложного кейса.' +
'</div></div>' +
'<div class="svc-order-card" style="border-color:#9f1239">' +
'<div style="font-size:13px;font-weight:700;color:#9f1239;margin-bottom:6px">⚖️ Совет экспертов</div>' +
'<div style="font-size:13px;color:#374151;margin-bottom:10px">' +
'Адвокат · Судья · Арбитражный управляющий · Пристав · Аналитик практики — ' +
'каждый даёт позицию по вашему делу. Opus синтезирует вердикт с шансами в суде.' +
'</div>' +
'<div style="font-size:15px;font-weight:700;margin-bottom:12px">от 2 990 ₽ · ~20 сек</div>' +
'<div style="display:flex;gap:8px;flex-wrap:wrap">' +
'<button class="btn btn-p" style="padding:8px 18px;font-size:13px" onclick="_runCouncil()">⚖️ Запустить совет</button>' +
'<button class="btn btn-o" style="padding:8px 18px;font-size:13px" onclick="_offerPartner(\'сложный кейс\')">👨‍⚖️ Юрист-партнёр</button>' +
'<button class="svc-btn-detail" onclick="this.closest(\'[id]\').remove()">Нет, спасибо</button>' +
'</div>' +
'</div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
function _runCouncil() {
var offer = document.getElementById('council-offer'); if(offer) offer.remove();
var text = (document.getElementById('el-paste').value || '').trim();
if (!text) { toast('Нет текста договора'); return; }
var wrap = document.querySelector('.chatwrap');
var progress = document.createElement('div');
progress.id = 'council-progress';
progress.innerHTML =
'<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'⚖️ Совет экспертов работает… <span id="council-timer">0 сек</span>' +
'</div></div>';
if (wrap) wrap.appendChild(progress);
progress.scrollIntoView({behavior:'smooth'});
var t0 = Date.now();
var timerInterval = setInterval(function(){
var el = document.getElementById('council-timer');
if (el) el.textContent = Math.round((Date.now()-t0)/1000) + ' сек';
}, 1000);
fetch(API_BASE + '/api/council', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
case_description: text,
deadlines: _contractDeadlines,
complexity_score: 3
})
})
.then(function(r){ return r.json(); })
.then(function(data){
clearInterval(timerInterval);
var pr = document.getElementById('council-progress'); if(pr) pr.remove();
_showCouncilResult(data);
})
.catch(function(e){
clearInterval(timerInterval);
var pr = document.getElementById('council-progress'); if(pr) pr.remove();
toast('Ошибка совета: ' + e.message);
});
}
function _showCouncilResult(data) {
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
// Карточки агентов
var agentCards = '';
var icons = {advocate:'⚖️', judge:'🏛️', arbitrator:'👨‍💼', bailiff:'🔨', precedents:'📚'};
if (data.agents) {
Object.keys(data.agents).forEach(function(key){
var a = data.agents[key];
agentCards +=
'<div style="border:1px solid #e5e7eb;border-radius:10px;padding:12px;margin-bottom:8px">' +
'<div style="font-weight:700;font-size:13px;margin-bottom:6px">' + (icons[key]||'•') + ' ' + a.label + '</div>' +
'<div style="font-size:13px;color:#374151;line-height:1.6">' + a.reply.replace(/\n/g,'<br>') + '</div>' +
'</div>';
});
}
var div = document.createElement('div');
div.innerHTML =
'<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble" style="max-width:100%"><div class="nm">Елена · Вердикт совета (' + (data.duration_sec||'?') + ' сек)</div>' +
'<div style="background:#fff5f7;border:1.5px solid #9f1239;border-radius:10px;padding:14px;margin:10px 0;font-size:14px;line-height:1.7">' +
(data.synthesis || '').replace(/\n/g,'<br>') +
'</div>' +
'<details style="margin-top:10px"><summary style="cursor:pointer;font-size:13px;color:#6b7280">Позиции экспертов ↓</summary>' +
'<div style="margin-top:10px">' + agentCards + '</div>' +
'</details>' +
'</div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
/* ── ВЫБОР DELIVERABLE ── */
const DELIVS = {
protocol: {
ttl:'Протокол разногласий', h2:'Протокол разногласий по вашему договору',
p1:['1 490 ₽','Без комментариев','Все спорные пункты — готовый список изменений без пояснений'],
p2:['2 190 ₽','С обоснованием','Те же пункты + правовое обоснование каждого требования'],
p3:['от 3 490 ₽','Партнёрская версия','Протокол с формулировками, выгодными обеим сторонам + Елена сопровождает'],
pitch:'Когда другая сторона получает просто список требований без объяснений — первая реакция почти всегда «нет». Это не злой умысел, так работает психология переговоров.<br><br><b>С обоснованием</b> каждое требование превращается из вашей «хотелки» в правовую норму. Контрагент уже не может просто отказать — ему придётся объяснить, почему закон не имеет значения. Практически никто не идёт на это.<br><br><b>Партнёрская версия</b> — Елена составляет текст так, чтобы контрагент сам хотел согласиться. Договор, где обе стороны видят свою выгоду, подписывают быстро и выполняют честно 💛',
def:'2 190 ₽'
},
redact: {
ttl:'Переработка с комментариями', h2:'Переработанный договор по вашему случаю',
p1:['1 990 ₽','Без комментариев','Новая редакция всех спорных пунктов — чистый текст без объяснений'],
p2:['2 990 ₽','С комментариями','Та же редакция + пояснение что изменено и зачем по каждому пункту'],
p3:['от 4 490 ₽','Партнёрская редакция','Договор учитывает интересы обеих сторон + Елена сопровождает до подписи'],
pitch:'Молча прийти с переделанным договором — контрагент чувствует давление и начинает защищаться. Переписка растягивается на недели.<br><br><b>С комментариями</b> вы открываете диалог: вот что изменено, вот почему это справедливо. Люди принимают изменения, когда понимают логику. Меньше раундов — быстрее к подписи.<br><br><b>Партнёрская редакция</b> — Елена рядом до финала. Если контрагент выдвигает встречные возражения, она держит вашу позицию и не даёт потерять то, что важно 💛',
def:'2 990 ₽'
},
clean: {
ttl:'Чистая редакция', h2:'Договор готов к подписанию',
p1:['1 790 ₽','Чистая редакция','Все пункты переписаны — договор готов к подписанию, без пояснений'],
p2:['2 690 ₽','Редакция с пояснениями','Тот же результат + объяснение каждого изменения для вас и контрагента'],
p3:['от 3 990 ₽','Партнёрская редакция','Чистый договор с балансом интересов + Елена рядом на переговорах'],
pitch:'Договор уже защищает вас — осталось только подписать. Но у контрагента будут вопросы: «почему именно так?» Без ответов на них переговоры затягиваются.<br><br><b>С пояснениями</b> он получает и текст, и понимание: что изменено, почему это честно. Вопросы снимаются до того, как возникли. Путь к подписи становится короче.<br><br><b>Партнёрская редакция</b> — Елена пишет так, что контрагент видит баланс, а не давление. Такие договоры подписывают без торга и выполняют без претензий 💛',
def:'2 690 ₽'
},
partner: {
ttl:'Партнёрская редакция', h2:'Договор, который устроит обе стороны',
p1:['2 990 ₽','Партнёрская редакция','Все пункты переработаны с учётом интересов обеих сторон, без сопровождения'],
p2:['3 990 ₽','Редакция с обоснованием','Тот же результат + аргументы для переговоров по каждому пункту'],
p3:['от 5 900 ₽','Полное сопровождение','Договор + Елена ведёт переписку и переговоры до финальной подписи'],
pitch:'Партнёрский подход — это не уступки. Это когда текст уже содержит ответ на главный вопрос контрагента: «почему мне это выгодно?». Когда ответ уже в договоре — не о чём торговаться.<br><br><b>С обоснованием</b> у вас есть готовые аргументы на любые встречные возражения. Вы входите в переговоры спокойно — Елена уже предусмотрела ходы за другую сторону.<br><br><b>Полное сопровождение</b> — Елена ведёт переписку сама, держит вашу позицию и доводит до финальной подписи. Ваша задача: согласовать итоговый текст 💛',
def:'3 990 ₽'
},
consult: {
ttl:'Консультация по договору', h2:'Разбор договора — ответы на ваши вопросы',
p1:['790 ₽','Базовый разбор','Задаёте до 5 вопросов по договору — AI отвечает развёрнуто по каждому пункту в течение 2 часов'],
p2:['1 490 ₽','Полный разбор','AI анализирует весь договор, находит все риски и отвечает на любые ваши вопросы без ограничений'],
p3:['от 2 490 ₽','Разбор + заключение','Полный AI-анализ + письменное заключение с перечнем рисков и конкретными рекомендациями'],
pitch:'Иногда важнее не документ, а понять — что именно подписываешь и почему это опасно.<br><br><b>Консультация в чате</b> — конкретные вопросы, чёткие ответы письменно. Всё сохраняется, можно перечитать.<br><br><b>Голосовой разбор</b> — живой разговор, юрист читает договор вместе с вами. 30 минут меняют картину полностью.<br><br><b>С заключением</b> — получаете и понимание, и документ на руках. Можно показать другой стороне как независимое правовое мнение 💛',
def:'1 490 ₽'
},
reply: {
ttl:'Ответ контрагенту', h2:'Ответ на возражения другой стороны',
p1:['990 ₽','Шаблон ответа','Готовый текст ответа на конкретный отказ — с вашей правовой позицией и нужным тоном'],
p2:['1 690 ₽','Развёрнутый ответ','Тот же ответ + аргументы на 23 хода вперёд: закрываем типичные встречные возражения заранее'],
p3:['от 2 990 ₽','Переговорное сопровождение','Елена ведёт переписку с контрагентом вместо вас — до момента согласия или официального отказа'],
pitch:'Когда контрагент отказывает — первый импульс: уступить или спорить наугад. Оба пути проигрышные.<br><br><b>Шаблон ответа</b> — правильная позиция в нужном тоне. Со ссылкой на норму. Контрагент понимает: вы знаете о чём говорите.<br><br><b>Развёрнутый ответ</b> — мы предусматриваем их следующий ход заранее. Вы входите в переписку с ответами на возражения, которые ещё не прозвучали.<br><br><b>Сопровождение</b> — Елена ведёт всю переписку. Вы только согласуете финальные ходы 💛',
def:'1 690 ₽'
}
};
function selectDeliv(key) {
_selDeliv = key;
const d = DELIVS[key];
document.getElementById('pay-ttl').textContent = d.ttl;
document.getElementById('pay-h2').textContent = d.h2;
document.getElementById('p1-price').textContent = d.p1[0];
document.getElementById('p1-name').textContent = d.p1[1];
document.getElementById('p1-desc').textContent = d.p1[2];
document.getElementById('p2-price').textContent = d.p2[0];
document.getElementById('p2-name').textContent = d.p2[1];
document.getElementById('p2-desc').textContent = d.p2[2];
document.getElementById('p3-price').textContent = d.p3[0];
document.getElementById('p3-name').textContent = d.p3[1];
document.getElementById('p3-desc').textContent = d.p3[2];
document.getElementById('pay-pitch').innerHTML = d.pitch;
// субтитр: тип договора из детекции
const ct = CTYPES[_curCtypeKey] || CTYPES.other;
const ctName = ct.name.charAt(0).toUpperCase() + ct.name.slice(1);
document.getElementById('pay-sub').textContent = ct.emoji + ' ' + ctName + ' · цена под ваш случай';
// сбросить выбор на средний план и показать инструкции
selectPlan(2);
go('pay');
}
/* ── ТОСТ ── */
let _toastT;
function toast(msg){
let el=document.getElementById('toast');
if(!el){el=document.createElement('div');el.id='toast';el.className='toast';document.body.appendChild(el);}
el.innerHTML='<span class="tk">✓</span>'+msg;
el.classList.add('show'); clearTimeout(_toastT);
_toastT=setTimeout(()=>el.classList.remove('show'),2600);
}
function go(id){document.querySelectorAll('.screen').forEach(s=>s.classList.toggle('on',s.id===id));
if(id==='admin' && typeof _initAdmin==='function') setTimeout(_initAdmin,50);
if(id==='start') { _hchatDone=false; var m=document.getElementById('hchat-msgs'); if(m){m.innerHTML='';} var r=document.getElementById('hchat-replies'); if(r)r.style.display='none'; var ir=document.getElementById('hchat-input-row'); if(ir)ir.style.display='none'; setTimeout(initHeroChat,300); var rm=document.getElementById('rchat-msgs'); if(rm)rm.innerHTML=''; var s1=document.getElementById('el-step1'); if(s1)s1.style.display=''; var ic=document.getElementById('intake-custom'); if(ic)ic.value=''; };
if(id==='pay') { setTimeout(_initPayScreen, 80); }
window.scrollTo(0,0);}
function _initPayScreen() {
// Восстановить сохранённые реквизиты B2B
try {
var saved = JSON.parse(localStorage.getItem('zashita_b2b') || 'null');
if (saved && saved.type) {
setClientType(saved.type);
var set = function(elId, val){ var el=document.getElementById(elId); if(el&&val) el.value=val; };
set('b2b-name', saved.name);
set('b2b-inn', saved.inn);
set('b2b-kpp', saved.kpp);
set('b2b-addr', saved.addr);
set('b2b-email', saved.email);
}
} catch(e){}
// Баланс
var credits = parseInt(localStorage.getItem('zashita_credits') || '0');
var balEl = document.getElementById('pay-bal-credits');
if (balEl) balEl.textContent = credits;
var useBtn = document.getElementById('pay-bal-use-btn');
if (useBtn) useBtn.style.display = credits > 0 ? '' : 'none';
var subEl = document.getElementById('pay-bal-sub');
if (subEl) subEl.textContent = credits > 0
? 'Можно списать кредит вместо оплаты'
: 'Пополните баланс или оплатите разово';
}
/* ── СВОЙ ЗАПРОС ── */
let _customOpen = false;
let _voiceRec = null;
function toggleCustomReq() {
_customOpen = !_customOpen;
const area = document.getElementById('custom-req-area');
const btn = document.getElementById('custom-req-btn');
area.style.display = _customOpen ? 'block' : 'none';
btn.textContent = _customOpen ? '✕ Закрыть' : '✏️ Нужен другой формат? Опишите задачу →';
if (_customOpen) {
area.scrollIntoView({ behavior: 'smooth' });
setTimeout(() => document.getElementById('custom-text').focus(), 350);
}
}
function toggleVoice() {
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
const btn = document.getElementById('custom-voice-btn');
if (!SR) { btn.title='Не поддерживается — введите текстом'; btn.style.opacity='.4'; return; }
if (_voiceRec) { _voiceRec.stop(); return; }
_voiceRec = new SR();
_voiceRec.lang = 'ru-RU';
_voiceRec.continuous = true;
_voiceRec.interimResults = true;
const ta = document.getElementById('custom-text');
const base = ta.value;
_voiceRec.onresult = e => {
let t = '';
for (let i = e.resultIndex; i < e.results.length; i++) t += e.results[i][0].transcript;
ta.value = (base ? base + ' ' : '') + t;
};
_voiceRec.onerror = _voiceRec.onend = () => {
_voiceRec = null;
btn.classList.remove('recording');
btn.innerHTML = '🎤';
};
_voiceRec.start();
btn.classList.add('recording');
btn.innerHTML = '⏹';
}
function submitCustomReq() {
const text = (document.getElementById('custom-text').value || '').trim();
if (!text) { document.getElementById('custom-text').focus(); return; }
if (_voiceRec) { _voiceRec.stop(); }
// Сохраняем в localStorage для анализа
const key = 'zashita_custom_delivs';
const arr = JSON.parse(localStorage.getItem(key) || '[]');
arr.push({
ts: new Date().toISOString(),
ctype: (document.getElementById('el-scan-type') || {}).textContent || '',
text: text
});
localStorage.setItem(key, JSON.stringify(arr));
renderCustomStats();
// Скрываем форму, показываем подтверждение
document.querySelector('.custom-input-block').style.display = 'none';
document.getElementById('custom-req-btn').style.display = 'none';
const short = text.length > 90 ? text.slice(0, 90) + '…' : text;
document.getElementById('custom-confirm-text').innerHTML =
`Записала! Передам юристу:<br><b>«${short}»</b><br><br>Свяжемся с вами в течение 24 часов. Обычно — быстрее 🙂`;
const conf = document.getElementById('custom-confirm');
conf.style.display = 'flex';
conf.scrollIntoView({ behavior: 'smooth' });
}
function renderCustomStats() {
const arr = JSON.parse(localStorage.getItem('zashita_custom_delivs') || '[]');
let el = document.getElementById('custom-stats-pill');
if (!arr.length) { if (el) el.style.display = 'none'; return; }
if (!el) {
el = document.createElement('div'); el.id = 'custom-stats-pill';
el.className = 'stats-pill'; el.style.bottom = '52px';
el.title = 'Открыть аналитику';
el.onclick = () => { renderCustomAdmin(); go('custom-admin'); };
document.body.appendChild(el);
}
el.style.display = 'block';
el.innerHTML = `<b>Свои запросы (${arr.length})</b><br>`
+ arr.slice(-3).map(r =>
`<span style="color:rgba(255,255,255,.6);font-size:10px">· ${(r.ctype||'').trim().slice(0,20)}${(r.text||'').slice(0,38)}</span>`
).join('<br>');
}
/* ── ПОДРОБНЫЕ ИНСТРУКЦИИ ПО ПЛАНУ ── */
let _selDeliv = 'protocol';
let _selPlan = 2;
let _curCtypeKey = 'other';
const PLAN_PITCH = {
protocol: {
1: `<b>Что вы получите</b><br>Готовый протокол разногласий — наши формулировки вместо ваших проблемных пунктов. Чистый файл, без пояснений.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Отправьте файл контрагенту — мессенджер, e-mail или распечатка.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Добавьте текст: <i>«Направляю протокол разногласий к договору. Прошу подтвердить принятие или предоставить мотивированный отказ в течение 5 рабочих дней».</i></span></div><div class="pi-step"><span class="pi-n">3</span><span>Если откажут без обоснования — потребуйте письменный мотивированный ответ. Чаще всего его не дают, и переговоры возобновляются на ваших условиях.</span></div><div class="pi-meta">⏱ Готово через 24 часа · При вопросах пишите Елене в чат</div>`,
2: `<b>Что вы получите</b><br>Протокол разногласий + к каждому пункту — ссылка на норму закона. Это превращает ваши требования из «хотелок» в правовые обязанности.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Отправьте протокол с сопроводительным текстом: <i>«Каждая позиция основана на нормах действующего законодательства, указанных в документе».</i></span></div><div class="pi-step"><span class="pi-n">2</span><span>Если возражают — один вопрос: <i>«Укажите норму права, на основании которой вы отклоняете это требование».</i> Практически никто не отвечает — это уже победа.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Если сложный ответ всё же пришёл — пришлите Елене, она подготовит вашу следующую реплику.</span></div><div class="pi-meta">⏱ Готово через 24 часа · Поддержка по ответам контрагента включена</div>`,
3: `<b>Что вы получите</b><br>Протокол, сформулированный так, чтобы контрагент сам захотел согласиться — без ощущения давления с вашей стороны.<br><br><span class="pi-tag pi-incl">✓ Включено</span><b>Что входит:</b><div class="pi-step"><span class="pi-n">1</span><span>Протокол с формулировками win-win по всем спорным пунктам.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Готовое сопроводительное письмо от вашего имени.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Елена отвечает на любые встречные возражения контрагента — вы пересылаете их нам, мы готовим вашу реплику.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Помогаем зафиксировать итоговую редакцию — до финальной подписи.</span></div><div class="pi-meta">⏱ Первый ответ: 4 часа · Финал: 4872 часа · Елена рядом на каждом шаге 💛</div>`
},
redact: {
1: `<b>Что вы получите</b><br>Новая редакция всех спорных пунктов договора — чистый текст, готовый заменить проблемные формулировки. Без пояснений.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Откройте оригинал договора. Замените помеченные пункты на наши формулировки.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Направьте обновлённый договор контрагенту: <i>«Прошу рассмотреть актуализированную редакцию договора и подтвердить согласие».</i></span></div><div class="pi-step"><span class="pi-n">3</span><span>Если контрагент запросит объяснения — закажите версию «С комментариями» или обратитесь к Елене отдельно.</span></div><div class="pi-meta">⏱ Готово через 24 часа · Подходит если контрагент уже готов к диалогу</div>`,
2: `<b>Что вы получите</b><br>Переработанный договор + к каждому изменённому пункту — короткое пояснение: что изменено, почему это справедливо, на какой закон опирается.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Отправьте документ контрагенту со словами: <i>«К каждому изменению приложено обоснование — это поможет нам быстрее прийти к согласию».</i></span></div><div class="pi-step"><span class="pi-n">2</span><span>Если контрагент не согласен с конкретным пунктом — пришлите его возражение Елене. Она подготовит контраргумент.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Комментарии снимают большинство вопросов до того, как они превращаются в споры — переписка сокращается в разы.</span></div><div class="pi-meta">⏱ Готово через 24 часа · По опыту: 70% договоров с комментариями подписывают с первого раунда</div>`,
3: `<b>Что вы получите</b><br>Договор, переработанный с учётом интересов обеих сторон — контрагент видит не давление, а честное предложение. Плюс Елена рядом до финала.<br><br><span class="pi-tag pi-incl">✓ Включено</span><b>Что входит:</b><div class="pi-step"><span class="pi-n">1</span><span>Полная переработка всех спорных пунктов — с балансом интересов.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Готовое сопроводительное письмо с нужной тональностью.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Елена держит вашу позицию при встречных возражениях — вы не теряете важное в процессе переговоров.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Финальная сверка перед подписанием: убеждаемся, что итоговая редакция вас защищает.</span></div><div class="pi-meta">⏱ Первый ответ: 4 часа · Поддержка до подписи включена 💛</div>`
},
clean: {
1: `<b>Что вы получите</b><br>Договор с переписанными спорными пунктами — готов к подписанию. Никаких пояснений, только чистый текст.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Получаете файл → сравниваете с оригиналом по выделенным пунктам → убеждаетесь, что всё устраивает.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Направляете контрагенту: <i>«Предлагаю подписать актуальную редакцию договора».</i></span></div><div class="pi-step"><span class="pi-n">3</span><span>Если контрагент спросит «почему изменено?» — вы можете дозаказать пояснения к уже готовому тексту. Или воспользоваться версией с обоснованием сразу.</span></div><div class="pi-meta">⏱ Готово через 24 часа · Подходит если контрагент уже согласен на изменения</div>`,
2: `<b>Что вы получите</b><br>Готовый к подписанию договор + пояснения к каждому изменению — для вас и для контрагента. Вы понимаете каждый пункт, контрагент понимает логику.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Изучите пояснения сами — убедитесь, что всё отражает ваши интересы.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Отправьте контрагенту договор вместе с пояснительной частью: <i>«К каждому изменению есть обоснование — посмотрите, это справедливо для обеих сторон».</i></span></div><div class="pi-step"><span class="pi-n">3</span><span>Вопросов по тексту практически не остаётся — путь к подписи короткий.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Если всё же есть спорный момент — напишите Елене, она поможет с ответом.</span></div><div class="pi-meta">⏱ Готово через 24 часа · Пояснения снимают 80% возражений на старте</div>`,
3: `<b>Что вы получите</b><br>Договор с балансом интересов, где контрагент видит свою выгоду — и соглашается без торга. Плюс Елена ведёт вас до подписи.<br><br><span class="pi-tag pi-incl">✓ Включено</span><b>Что входит:</b><div class="pi-step"><span class="pi-n">1</span><span>Договор переписан так, чтобы обе стороны видели: условия честные.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Сопроводительное письмо с нужной тональностью для переговоров.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Если контрагент возражает — Елена готовит ответ за вас. Вы только согласуете финальный текст.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Перед подписанием — финальная проверка: убеждаемся, что ничего важного не потеряно в процессе переговоров.</span></div><div class="pi-meta">⏱ Первый ответ: 4 часа · Договоры с балансом подписывают и выполняют честно 💛</div>`
},
consult: {
1: `<b>Что вы получите</b><br>Письменный разбор: задаёте до 5 вопросов по договору — AI отвечает на каждый развёрнуто в течение 2 часов.<br><br><b>Как работает:</b><div class="pi-step"><span class="pi-n">1</span><span>После оплаты Елена откроет чат и пришлёт инструкцию.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Отправляете текст договора + список вопросов (или пишете «найди риски сам»).</span></div><div class="pi-step"><span class="pi-n">3</span><span>AI разбирает каждый вопрос письменно — конкретно, со ссылкой на закон и рекомендацией.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Переписку можно сохранить и использовать в переговорах.</span></div><div class="pi-meta">⏱ Ответ в течение 2 часов · Подходит если есть конкретные вопросы</div>`,
2: `<b>Что вы получите</b><br>Полный AI-разбор всего договора: все риски, проблемные пункты, ответы на любые вопросы без ограничений.<br><br><b>Как работает:</b><div class="pi-step"><span class="pi-n">1</span><span>После оплаты Елена откроет чат.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Отправляете договор — AI сам проходит весь текст и находит все риски.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Задаёте любые вопросы — AI отвечает конкретно по вашей ситуации. Без ограничений по количеству.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Понимаете что подписываете — и что делать дальше.</span></div><div class="pi-meta">⏱ Первый ответ в течение 2 часов · Вопросы без ограничений</div>`,
3: `<b>Что вы получите</b><br>Полный AI-анализ + письменное заключение: перечень всех рисков, конкретные рекомендации, ссылки на нормы закона.<br><br><span class="pi-tag pi-incl">✓ Включено</span><b>Что входит:</b><div class="pi-step"><span class="pi-n">1</span><span>AI проходит весь договор и находит все риски — критичные, средние, незначительные.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Отвечает на ваши вопросы без ограничений — по каждому пункту, по вашей ситуации.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Письменное заключение: риски, рекомендации, нормы закона — документ у вас на руках.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Заключение можно показать другой стороне как независимую экспертизу.</span></div><div class="pi-meta">⏱ Разбор в течение 2 часов · Заключение: в течение 24 часов 💛</div>`
},
reply: {
1: `<b>Что вы получите</b><br>Готовый текст ответа на конкретный отказ или возражение контрагента — с вашей правовой позицией, в нужном тоне.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Пришлите нам текст возражения контрагента и ваш договор.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Юрист составляет ответ: спокойный тон + чёткая позиция + ссылка на норму права.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Отправляете контрагенту без изменений или адаптируете под свой стиль.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Такой ответ показывает: вы знаете о чём говорите. Большинство контрагентов после этого соглашаются.</span></div><div class="pi-meta">⏱ Готово через 12 часов · Работает на любом этапе переговоров</div>`,
2: `<b>Что вы получите</b><br>Развёрнутый ответ + аргументы на 23 шага вперёд: мы предусматриваем их следующие возражения и закрываем их заранее.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Пришлите текст возражения контрагента и все предыдущие сообщения по переговорам.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Юрист анализирует всю цепочку и составляет ответ с закрытием типичных контраргументов.</span></div><div class="pi-step"><span class="pi-n">3</span><span>В документе — основной ответ + «шпаргалка» на случай если всё же возразят снова.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Вы входите в следующий раунд уже подготовленными.</span></div><div class="pi-meta">⏱ Готово через 12 часов · Включает шпаргалку для продолжения диалога</div>`,
3: `<b>Что вы получите</b><br>Елена ведёт переписку с контрагентом от вашего имени — до тех пор пока сторона не соглашается или не фиксирует официальный отказ.<br><br><span class="pi-tag pi-incl">✓ Включено</span><b>Как работает:</b><div class="pi-step"><span class="pi-n">1</span><span>Передаёте нам историю переписки и доступ к каналу общения (или пересылаете сообщения).</span></div><div class="pi-step"><span class="pi-n">2</span><span>Елена готовит каждый ответ — вы видите текст до отправки и можете скорректировать.</span></div><div class="pi-step"><span class="pi-n">3</span><span>При нестандартных ходах контрагента — юрист адаптирует стратегию в режиме реального времени.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Финал: либо подписанное соглашение, либо официальный отказ — с основанием для суда если потребуется.</span></div><div class="pi-meta">⏱ Первый ответ: 4 часа · Ведём до результата 💛</div>`
},
partner: {
1: `<b>Что вы получите</b><br>Договор, переработанный с учётом интересов обеих сторон. Контрагент не чувствует давления — он видит предложение, от которого сложно отказаться.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Изучите договор — обратите внимание на пункты с пометкой «↔ обе стороны»: в них мы специально вписали выгоду для контрагента.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Отправьте с простым сопроводительным текстом: <i>«Подготовил(а) редакцию, которая учитывает интересы обеих сторон — посмотри».</i></span></div><div class="pi-step"><span class="pi-n">3</span><span>При возражениях — обратитесь к Елене отдельно или закажите версию с сопровождением.</span></div><div class="pi-meta">⏱ Готово через 2448 часов · Партнёрские договоры подписывают без торга</div>`,
2: `<b>Что вы получите</b><br>Партнёрская редакция + к каждому пункту — аргумент, почему это выгодно для контрагента. У вас есть ответ на любое возражение ещё до того, как оно прозвучало.<br><br><b>Как использовать:</b><div class="pi-step"><span class="pi-n">1</span><span>Изучите аргументы — они написаны под вашу конкретную ситуацию, не шаблонные.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Отправьте договор контрагенту. Если он не согласен с каким-то пунктом — у вас уже готов ответ.</span></div><div class="pi-step"><span class="pi-n">3</span><span>Нестандартная реакция — пришлите Елене, она поможет с ответной позицией.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Вы входите в переговоры с полной картиной — спокойно и уверенно.</span></div><div class="pi-meta">⏱ Готово через 2448 часов · Поддержка по возражениям включена</div>`,
3: `<b>Что вы получите</b><br>Елена ведёт переговоры вместе с вами — от отправки договора до финальной подписи. Ваша задача: согласовать итоговый текст.<br><br><span class="pi-tag pi-incl">✓ Включено</span><b>Полное сопровождение:</b><div class="pi-step"><span class="pi-n">1</span><span>Партнёрская редакция договора + сопроводительное письмо от вашего имени.</span></div><div class="pi-step"><span class="pi-n">2</span><span>Елена анализирует ответ контрагента и готовит вашу следующую реплику.</span></div><div class="pi-step"><span class="pi-n">3</span><span>При встречных правках — определяем, что можно уступить без потерь, а что держим твёрдо.</span></div><div class="pi-step"><span class="pi-n">4</span><span>Финальная проверка перед подписанием: убеждаемся, что ваша защита сохранена в итоговом тексте.</span></div><div class="pi-meta">⏱ Первый ответ: 4 часа · Ведём до подписи · Среднее время закрытия сделки: 35 дней 💛</div>`
}
};
function selectPlan(n) {
_selPlan = n;
// подсветка
[1,2,3].forEach(i => {
const el = document.getElementById('pay-plan-' + i);
if (el) el.classList.toggle('sel', i === n);
});
// цена в кнопке
const priceEl = document.getElementById('p' + n + '-price');
if (priceEl) {
const priceText = priceEl.textContent;
const btn = document.getElementById('pay-price-btn');
if (btn) btn.textContent = 'Оплатить ' + priceText.replace('от ', '');
}
// инструкции Елены
const pitchEl = document.getElementById('pay-pitch');
if (pitchEl && PLAN_PITCH[_selDeliv] && PLAN_PITCH[_selDeliv][n]) {
pitchEl.innerHTML = PLAN_PITCH[_selDeliv][n];
}
}
/* ── ВЫБОР ТИПА КЛИЕНТА + B2B ФОРМА ── */
var _clientType = 'fl'; // fl | ip | ooo
var _b2bFiles = []; // {name, size, dataUrl}
var _b2bPhoto = null; // dataUrl
var _cameraStream = null;
function setClientType(type) {
_clientType = type;
['fl','ip','ooo'].forEach(function(t){
var el = document.getElementById('ctt-' + t);
if (el) el.classList.toggle('act', t === type);
});
var form = document.getElementById('b2b-form');
if (form) form.classList.toggle('on', type !== 'fl');
// КПП — только для ООО/АО
var kppWrap = document.getElementById('b2b-kpp-wrap');
if (kppWrap) kppWrap.style.display = (type === 'ooo') ? '' : 'none';
// placeholder ИНН
var inn = document.getElementById('b2b-inn');
if (inn) inn.placeholder = (type === 'ooo') ? '10 цифр' : '12 цифр';
}
function _validateB2B() {
if (_clientType === 'fl') return true;
var name = (document.getElementById('b2b-name').value || '').trim();
var inn = (document.getElementById('b2b-inn').value || '').trim();
var ok = true;
if (!name) { document.getElementById('b2b-name').classList.add('err'); ok = false; }
else document.getElementById('b2b-name').classList.remove('err');
var innLen = _clientType === 'ooo' ? 10 : 12;
if (!inn || inn.length !== innLen || !/^\d+$/.test(inn)) {
document.getElementById('b2b-inn').classList.add('err'); ok = false;
} else {
document.getElementById('b2b-inn').classList.remove('err');
}
if (!ok) toast('⚠️ Заполните обязательные поля: Название и ИНН');
return ok;
}
function _getB2BData() {
if (_clientType === 'fl') return null;
return {
type: _clientType,
name: (document.getElementById('b2b-name').value || '').trim(),
inn: (document.getElementById('b2b-inn').value || '').trim(),
kpp: (document.getElementById('b2b-kpp').value || '').trim(),
addr: (document.getElementById('b2b-addr').value || '').trim(),
email: (document.getElementById('b2b-email').value || '').trim(),
files: _b2bFiles.map(function(f){ return {name: f.name, size: f.size}; }),
photo: !!_b2bPhoto,
};
}
/* Файлы */
function b2bFileSelected(input) {
var files = Array.from(input.files || []);
files.forEach(function(file){
if (file.size > 10 * 1024 * 1024) { toast('Файл ' + file.name + ' слишком большой (макс 10 МБ)'); return; }
var reader = new FileReader();
reader.onload = function(e){
_b2bFiles.push({ name: file.name, size: file.size, dataUrl: e.target.result });
_renderB2BFiles();
};
reader.readAsDataURL(file);
});
input.value = '';
var btn = document.getElementById('b2b-file-btn');
if (btn) btn.classList.toggle('has-file', _b2bFiles.length > 0);
}
function _renderB2BFiles() {
var list = document.getElementById('b2b-files-list');
if (!list) return;
list.innerHTML = _b2bFiles.map(function(f, i){
var kb = Math.round(f.size / 1024);
return '<div class="b2b-file-chip">📎 ' + f.name + ' <span style="color:var(--mut)">(' + kb + ' КБ)</span>' +
'<span class="rm" onclick="b2bRemoveFile(' + i + ')">✕</span></div>';
}).join('');
}
function b2bRemoveFile(idx) {
_b2bFiles.splice(idx, 1);
_renderB2BFiles();
var btn = document.getElementById('b2b-file-btn');
if (btn) btn.classList.toggle('has-file', _b2bFiles.length > 0);
}
/* Камера */
function b2bOpenCamera() {
var wrap = document.getElementById('b2b-camera-wrap');
if (!wrap) return;
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
// Fallback: file input с capture
var inp = document.createElement('input');
inp.type = 'file'; inp.accept = 'image/*'; inp.capture = 'environment';
inp.onchange = function(){ b2bFileSelected(inp); };
inp.click();
return;
}
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
.then(function(stream){
_cameraStream = stream;
var video = document.getElementById('b2b-video');
if (video) { video.srcObject = stream; }
wrap.classList.add('on');
var btn = document.getElementById('b2b-camera-btn');
if (btn) btn.style.display = 'none';
})
.catch(function(e){
toast('Нет доступа к камере: ' + (e.message || e));
});
}
function b2bSnap() {
var video = document.getElementById('b2b-video');
var canvas = document.getElementById('b2b-canvas');
if (!video || !canvas) return;
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 480;
canvas.getContext('2d').drawImage(video, 0, 0);
_b2bPhoto = canvas.toDataURL('image/jpeg', 0.85);
b2bCloseCamera();
// Показываем превью
var prev = document.getElementById('b2b-photo-preview');
var img = document.getElementById('b2b-photo-img');
if (prev && img) { img.src = _b2bPhoto; prev.style.display = 'block'; }
var btn = document.getElementById('b2b-camera-btn');
if (btn) btn.classList.add('has-file');
}
function b2bCloseCamera() {
if (_cameraStream) {
_cameraStream.getTracks().forEach(function(t){ t.stop(); });
_cameraStream = null;
}
var wrap = document.getElementById('b2b-camera-wrap');
if (wrap) wrap.classList.remove('on');
var btn = document.getElementById('b2b-camera-btn');
if (btn) btn.style.display = '';
}
function b2bRemovePhoto() {
_b2bPhoto = null;
var prev = document.getElementById('b2b-photo-preview');
if (prev) prev.style.display = 'none';
var btn = document.getElementById('b2b-camera-btn');
if (btn) btn.classList.remove('has-file');
}
/* ── ЮKASSA ВИДЖЕТ ── */
function ykOpen() {
const priceEl = document.getElementById('pay-price-btn');
const price = priceEl ? priceEl.textContent.replace('Оплатить ', '') : '';
const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
set('yk-amount', price);
set('yk-btn-amount', price);
document.getElementById('yk-overlay').classList.add('open');
document.getElementById('yk-form').style.display = '';
document.getElementById('yk-success').style.display = 'none';
document.getElementById('yk-card').value = '';
document.getElementById('yk-exp').value = '';
document.getElementById('yk-cvv').value = '';
}
function ykClose(e) {
if (e && e.target !== document.getElementById('yk-overlay')) return;
document.getElementById('yk-overlay').classList.remove('open');
}
function ykFmtCard(el) {
let v = el.value.replace(/\D/g,'').slice(0,16);
el.value = v.match(/.{1,4}/g)?.join(' ') || v;
}
function ykFmtExp(el) {
let v = el.value.replace(/\D/g,'').slice(0,4);
if (v.length >= 3) v = v.slice(0,2) + ' / ' + v.slice(2);
el.value = v;
}
function ykPaySBP() {
// В проде: редирект на deeplink СБП с payment_id
ykFinish();
}
function ykSubmit() {
const card = document.getElementById('yk-card').value.replace(/\s/g,'');
const exp = document.getElementById('yk-exp').value;
const cvv = document.getElementById('yk-cvv').value;
let ok = true;
if (card.length < 16) { document.getElementById('yk-card').classList.add('yk-error'); ok = false; }
else document.getElementById('yk-card').classList.remove('yk-error');
if (exp.length < 7) { document.getElementById('yk-exp').classList.add('yk-error'); ok = false; }
else document.getElementById('yk-exp').classList.remove('yk-error');
if (cvv.length < 3) { document.getElementById('yk-cvv').classList.add('yk-error'); ok = false; }
else document.getElementById('yk-cvv').classList.remove('yk-error');
if (!ok) return;
const btn = document.getElementById('yk-pay-btn');
btn.disabled = true;
btn.innerHTML = '<div class="yk-spinner" style="display:inline-block"></div>';
// В проде: POST на /api/payment → получить payment_id → confirm
setTimeout(ykFinish, 1600);
}
function ykFinish() {
document.getElementById('yk-form').style.display = 'none';
document.getElementById('yk-success').style.display = 'block';
setTimeout(() => {
document.getElementById('yk-overlay').classList.remove('open');
// Сохраняем последний заказ для приветствия
try {
const d = DELIVS[_selDeliv] || {};
const p = d['p'+_selPlan] || [];
localStorage.setItem('zashita_last_order', JSON.stringify({ ttl: d.ttl||'', plan: p[1]||'', price: p[0]||'' }));
} catch(e) {}
showOrderStatus();
go('order-status');
}, 1200);
}
function startTypewriter(el, phrases, speed) {
if (!el) return;
var pi = 0, ci = 0, deleting = false, pause = 0;
speed = speed || 62;
function tick() {
var ph = phrases[pi];
if (pause > 0) { pause--; setTimeout(tick, 80); return; }
if (!deleting) {
ci++;
el.textContent = ph.slice(0, ci);
if (ci >= ph.length) { deleting = true; pause = 32; }
} else {
ci--;
el.textContent = ph.slice(0, ci);
if (ci <= 0) { deleting = false; pause = 10; pi = (pi + 1) % phrases.length; }
}
setTimeout(tick, deleting ? Math.round(speed / 3) : speed);
}
tick();
}
function checkReturning() {
const stats = JSON.parse(localStorage.getItem('zashita_intake_stats') || '[]');
const lastOrder = JSON.parse(localStorage.getItem('zashita_last_order') || 'null');
const isReturning = stats.length > 0 || !!lastOrder;
const n = document.getElementById('hero-new');
const r = document.getElementById('hero-returning');
if (!isReturning) {
// ── НОВЫЙ КЛИЕНТ: typewriter с USP-фразами
if (n) n.style.display = '';
if (r) r.style.display = 'none';
startTypewriter(document.getElementById('hero-tw-new'), [
'Первые 3 риска — бесплатно',
'Результат за несколько секунд',
'Работаю 24/7 — без очередей и звонков',
'Данные только на вашем устройстве 🔒',
'Защищаю людей — не только бизнес',
], 68);
} else {
// ── ВЕРНУВШИЙСЯ: personalized typewriter
if (n) n.style.display = 'none';
if (r) r.style.display = '';
// карточка последнего заказа
if (lastOrder && lastOrder.ttl) {
const el = document.getElementById('ret-last-order');
if (el) el.innerHTML =
'<div class="ret-ord-lbl">Последний заказ</div>' +
'<div class="ret-ord-name">' + lastOrder.ttl + ' · ' + lastOrder.plan + '</div>' +
'<div class="ret-ord-price">' + lastOrder.price + '</div>';
}
// персональные фразы
var retPhrases = ['Готова продолжить — чем Вам помочь сегодня?'];
if (lastOrder && lastOrder.ttl) {
retPhrases.unshift('ваш договор на проверке: ' + lastOrder.ttl);
}
retPhrases.push('новый документ — за несколько секунд');
retPhrases.push('все ваши дела в кабинете 📂');
// personalized chat instead of typewriter
setTimeout(initReturnChat, 300);
}
}
window.addEventListener('DOMContentLoaded', checkReturning);
/* ── MS-PROJECT GANTT ── */
(function(){
// Данные
var DATA = [
{ num:1, group:true, name:'🍽️ Кухня — агентский', dur:'18 дн', start:'23.05', end:'10.06', s:'2025-05-23', e:'2025-06-10', cls:'b-group', go:"tab('case')" },
{ num:2, group:false, name:' Анализ рисков', dur:'2 дн', start:'23.05', end:'24.05', s:'2025-05-23', e:'2025-05-24', cls:'b-done', go:"tab('case')" },
{ num:3, group:false, name:' Протокол разногласий', dur:'3 дн', start:'24.05', end:'27.05', s:'2025-05-24', e:'2025-05-27', cls:'b-urgent', go:"tab('case')" },
{ num:4, group:false, name:' Ответ контрагенту', dur:'2 дн', start:'27.05', end:'29.05', s:'2025-05-27', e:'2025-05-29', cls:'b-pending', go:"tab('case')" },
{ num:5, group:true, name:'💼 Трудовой договор', dur:'15 дн', start:'21.05', end:'05.06', s:'2025-05-21', e:'2025-06-05', cls:'b-group', go:"tab('case')" },
{ num:6, group:false, name:' Первичный анализ', dur:'1 дн', start:'21.05', end:'22.05', s:'2025-05-21', e:'2025-05-22', cls:'b-done', go:"tab('case')" },
{ num:7, group:false, name:' Допсоглашение', dur:'8 дн', start:'22.05', end:'30.05', s:'2025-05-22', e:'2025-05-30', cls:'b-active', go:"tab('case')" },
{ num:8, group:false, name:' Проверка финала', dur:'5 дн', start:'30.05', end:'05.06', s:'2025-05-30', e:'2025-06-05', cls:'b-pending', go:"tab('case')" },
{ num:9, group:true, name:'🏠 ДДУ (новая редакция)', dur:'17 дн', start:'19.05', end:'05.06', s:'2025-05-19', e:'2025-06-05', cls:'b-group', go:"tab('case')" },
{ num:10, group:false, name:' Сверка версий', dur:'8 дн', start:'19.05', end:'27.05', s:'2025-05-19', e:'2025-05-27', cls:'b-active', go:"tab('case')" },
{ num:11, group:false, name:' Финальное заключение', dur:'9 дн', start:'27.05', end:'05.06', s:'2025-05-27', e:'2025-06-05', cls:'b-pending', go:"tab('case')" },
];
var RS = new Date('2025-05-17T00:00:00');
var RE = new Date('2025-06-15T00:00:00');
var TOT = (RE - RS) / 86400000;
function pct(str) {
var dt = new Date(str + 'T00:00:00');
return Math.max(0, Math.min(100, (dt - RS) / 86400000 / TOT * 100));
}
// Построить тики дат
function buildDateTicks() {
var ticks = []; var cur = new Date(RS);
while(cur <= RE) { ticks.push(new Date(cur)); cur.setDate(cur.getDate()+7); }
return ticks;
}
// Построить сетку (колонки = дни)
function buildGridCols() {
var cols = ''; var cur = new Date(RS);
while(cur < RE) {
var d = cur.getDay();
cols += '<div class="msp-grid-col' + (d===0||d===6?' weekend':'') + '"></div>';
cur.setDate(cur.getDate()+1);
}
return cols;
}
function render() {
var root = document.getElementById('msp-root');
if (!root) return;
var today = new Date(); today.setHours(0,0,0,0);
var todayPct = pct(today.toISOString().slice(0,10));
var ticks = buildDateTicks();
var gridCols = buildGridCols();
var m = ['янв','фев','мар','апр','май','июн','июл','авг','сен','окт','ноя','дек'];
// Шапка дат
var ticksHTML = ticks.map(function(dt){
return '<div class="msp-date-tick" style="left:'+pct(dt.toISOString().slice(0,10)).toFixed(2)+'%">'+
dt.getDate()+' '+m[dt.getMonth()]+'</div>';
}).join('');
// HEAD
var head = '<div class="msp-head">' +
'<div class="msp-left">' +
'<div class="msp-hcol msp-c-num">#</div>' +
'<div class="msp-hcol msp-c-name">Название задачи</div>' +
'<div class="msp-hcol msp-c-dur">Длит.</div>' +
'<div class="msp-hcol msp-c-start">Начало</div>' +
'<div class="msp-hcol msp-c-end">Окончание</div>' +
'</div>' +
'<div class="msp-right">' +
'<div class="msp-date-hdr">' + ticksHTML + '</div>' +
'</div>' +
'</div>';
// ROWS
var rows = DATA.map(function(r){
var left_pct = pct(r.s);
var right_pct = pct(r.e);
var w = Math.max(0.5, right_pct - left_pct);
var barLabel = r.group ? '' : r.end;
var isDone = r.cls === 'b-done';
var isActive = r.cls === 'b-active' || r.cls === 'b-urgent';
var rowCls = 'msp-row' + (r.group?' msp-group':'') + (isDone?' msp-done':'') + (isActive?' msp-active-row':'');
return '<div class="'+rowCls+'" onclick="'+r.go+'">' +
'<div class="msp-left">' +
'<div class="msp-cell msp-c-num">'+r.num+'</div>' +
'<div class="msp-cell msp-c-name'+(r.group?'':' ind')+'">'+(isDone&&!r.group?'✅ ':'')+r.name+'</div>' +
'<div class="msp-cell msp-c-dur">'+r.dur+'</div>' +
'<div class="msp-cell msp-c-start">'+r.start+'</div>' +
'<div class="msp-cell msp-c-end">'+r.end+'</div>' +
'</div>' +
'<div class="msp-right">' +
'<div class="msp-grid-bg">'+gridCols+'</div>' +
'<div class="msp-bar-wrap">' +
'<div class="msp-today" style="left:'+todayPct.toFixed(2)+'%"></div>' +
'<div class="msp-bar '+r.cls+'" style="left:'+left_pct.toFixed(2)+'%;width:'+w.toFixed(2)+'%">'+barLabel+'</div>' +
'</div>' +
'</div>' +
'</div>';
}).join('');
root.innerHTML = head + rows;
}
// Рендерим при открытии кабинета и при tab('cases')
var _origTab = window.tab;
window.tab = function(id) {
if (_origTab) _origTab(id);
if (id === 'cases') setTimeout(render, 50);
};
var _origGo = window.go;
window.go = function(id) {
if (_origGo) _origGo(id);
if (id === 'cabinet') setTimeout(render, 80);
};
window.addEventListener('DOMContentLoaded', function(){
var el = document.getElementById('p-cases');
if (el && el.classList.contains('on')) render();
});
})();
/* ── ТАБЛИЦА ДОГОВОРОВ ── */
(function(){
// Статусная модель:
// active — договор подписан, исполняется, сроки отслеживаются
// dispute — спор активен: претензия / суд / взыскание
// wait — ожидает оплаты или загрузки документа
// completed — дело закрыто (успех или соглашение)
// archived — в архиве (истёк срок, отказ, не актуально)
// Иконки по типу договора
var TYPE_ICO = {
'Аренда':'🏠','Договор аренды':'🏠','Подряд':'🔨','Договор подряда':'🔨',
'Купли-продажи':'🛒','Договор купли-продажи':'🛒','Трудовой':'💼',
'Агентский':'🤝','Займ':'💰','Расписка':'📝','Акт':'✅','ДДУ':'🏗️',
};
// Загружаем реальные договоры из localStorage
function _buildCTData() {
var saved = typeof _getContracts === 'function' ? _getContracts() : [];
var real = saved.map(function(c, i) {
var d = new Date(c.ts || Date.now());
var dateStr = d.getDate() + '.' + (d.getMonth()+1 < 10 ? '0' : '') + (d.getMonth()+1);
var dateSort = parseInt(d.toISOString().slice(0,10).replace(/-/g,''));
var risk = c.risks_critical > 0 ? 'high' : c.deadlines_count > 2 ? 'mid' : 'low';
var riskLbl = risk === 'high' ? '⚠ Высокий' : risk === 'mid' ? 'Средний' : 'Низкий';
var ico = TYPE_ICO[c.type] || '📄';
var name = c.type + (c.counterparty ? ' · ' + c.counterparty : '');
return {
id: 'case-real-' + i,
ico: ico, name: name, type: c.type || 'Договор',
date: dateStr, dateSort: dateSort,
risk: risk, riskLbl: riskLbl,
status: 'active',
quality: c.quality, // оценка качества если есть
deadlines_count: c.deadlines_count || 0,
go: "_openRealCase(" + i + ")"
};
});
// Если реальных нет — показываем демо
if (!real.length) {
return [
{ id:'case-kitchen', ico:'🍽️', name:'Кухня — агентский (ЗОВ)', type:'Агентский', date:'23.05', dateSort:20250523, risk:'high', riskLbl:'⚠ Высокий', status:'active', go:"tab('case')" },
{ id:'case-labor', ico:'💼', name:'Трудовой договор', type:'Трудовой', date:'21.05', dateSort:20250521, risk:'mid', riskLbl:'Средний', status:'dispute', go:"tab('case')" },
{ id:'case-ddu', ico:'🏠', name:'Квартира — ДДУ (новая ред.)',type:'ДДУ', date:'19.05', dateSort:20250519, risk:'low', riskLbl:'Низкий', status:'wait', go:"tab('case')" },
{ id:'case-office', ico:'📄', name:'Аренда офиса 2024', type:'Аренда', date:'12.03', dateSort:20250312, risk:'low', riskLbl:'Низкий', status:'completed', go:"toast('Архивное дело')" },
{ id:'case-supply', ico:'📄', name:'Поставка оборудования', type:'Поставка',date:'01.02', dateSort:20250201, risk:'mid', riskLbl:'Средний', status:'archived', go:"toast('Архивное дело')" },
];
}
return real;
}
var CT_DATA = _buildCTData();
// Экспортируем для Елены
window._CT_DATA = CT_DATA;
var STATUS_MAP = {
'active': { lbl:'🟢 Активен', cls:'chip ok' },
'dispute': { lbl:'⚔️ Спор', cls:'chip d' },
'wait': { lbl:'⏳ Ожидает документ', cls:'chip w' },
'completed': { lbl:'✅ Завершён', cls:'chip n' },
'archived': { lbl:'📦 Архив', cls:'chip n' },
};
var _filter = 'all';
var _sortField = 'date';
var _sortDir = -1; // -1 = desc (новые сверху)
function riskOrder(r){ return r==='high'?0:r==='mid'?1:2; }
function filtered() {
return CT_DATA.filter(function(r){
if (_filter === 'active') return r.status === 'active';
if (_filter === 'dispute') return r.status === 'dispute';
if (_filter === 'done') return r.status === 'completed' || r.status === 'archived';
if (_filter === 'risk') return r.risk === 'high';
return true;
}).sort(function(a,b){
var va, vb;
if (_sortField === 'date') { va=a.dateSort; vb=b.dateSort; }
else if (_sortField === 'risk') { va=riskOrder(a.risk); vb=riskOrder(b.risk); }
else if (_sortField === 'name') { va=a.name; vb=b.name; }
else if (_sortField === 'type') { va=a.type; vb=b.type; }
else if (_sortField === 'status') { va=a.status; vb=b.status; }
else { va=a.dateSort; vb=b.dateSort; }
if (va < vb) return -1 * _sortDir;
if (va > vb) return 1 * _sortDir;
return 0;
});
}
function riskChipCls(r){ return r==='high'?'chip d':r==='mid'?'chip w':'chip n'; }
function render() {
var tbody = document.getElementById('ct-tbody');
if (!tbody) return;
// KPI из реальных данных
var kpiTotal = CT_DATA.length;
var kpiWork = CT_DATA.filter(function(r){ return r.status === 'active' || r.status === 'dispute' || r.status === 'wait'; }).length;
var kpiDone = CT_DATA.filter(function(r){ return r.status === 'completed' || r.status === 'archived'; }).length;
// Срочные = active/dispute с горящими дедлайнами
var activeCaseIds = CT_DATA
.filter(function(r){ return r.status === 'active' || r.status === 'dispute'; })
.map(function(r){ return r.id; });
var today = new Date(); today.setHours(0,0,0,0);
var kpiUrg = 0;
if (typeof _DEADLINES !== 'undefined') {
var urgCases = new Set();
_DEADLINES.forEach(function(d){
if (!d.done && d.date && activeCaseIds.indexOf(d.caseId) !== -1) {
var dt = new Date(d.date); dt.setHours(0,0,0,0);
var diff = Math.round((dt - today) / 86400000);
if (diff >= -1 && diff <= 7) urgCases.add(d.caseId);
}
});
kpiUrg = urgCases.size;
}
var elTotal = document.getElementById('kpi-total'); if (elTotal) elTotal.textContent = kpiTotal;
var elWork = document.getElementById('kpi-work'); if (elWork) elWork.textContent = kpiWork;
var elUrg = document.getElementById('kpi-urg'); if (elUrg) elUrg.textContent = kpiUrg;
var elDone = document.getElementById('kpi-done'); if (elDone) elDone.textContent = kpiDone;
// Перегенерируем с реальными данными
CT_DATA = _buildCTData();
window._CT_DATA = CT_DATA;
var rows = filtered();
if (!rows.length) {
tbody.innerHTML = '<tr><td colspan="6" style="padding:24px;text-align:center;color:var(--mut)">' +
'Нет дел. <a style="color:var(--bg);cursor:pointer" onclick="go(\'elena\')">Загрузите договор</a> — Елена добавит его сюда автоматически.</td></tr>';
return;
}
var closed = ['completed','archived'];
tbody.innerHTML = rows.map(function(r){
var isClosed = closed.indexOf(r.status) !== -1;
// Quality badge (только для реальных загруженных договоров)
var qualBadge = '';
if (r.quality !== undefined) {
var qScore = typeof r.quality === 'object' ? r.quality.score : r.quality;
var qColor = qScore >= 80 ? '#16a34a' : qScore >= 50 ? '#d97706' : '#dc2626';
var qLabel = qScore >= 80 ? '✅' : qScore >= 50 ? '🟡' : '⚠️';
qualBadge = ' <span title="Качество распознавания: ' + qScore + '%" style="font-size:11px;color:' + qColor + '">' + qLabel + qScore + '%</span>';
}
// Дедлайны по делу
var dlBadge = '';
if (r.deadlines_count > 0) {
dlBadge = ' <span style="font-size:11px;color:var(--mut)">📅' + r.deadlines_count + '</span>';
}
return '<tr class="'+(isClosed?'ct-closed':'')+'" onclick="'+r.go+'">' +
'<td class="ct-name">'+r.ico+' '+r.name+qualBadge+dlBadge+'</td>' +
'<td><span class="ct-type-badge">'+r.type+'</span></td>' +
'<td style="color:var(--mut)">'+r.date+'</td>' +
'<td><span class="'+riskChipCls(r.risk)+'">'+r.riskLbl+'</span></td>' +
'<td><span class="'+(STATUS_MAP[r.status]||{cls:'chip n'}).cls+'">'+(STATUS_MAP[r.status]||{lbl:r.status}).lbl+'</span></td>' +
'<td class="ct-open">→</td>' +
'</tr>';
}).join('');
}
window.ctFilter = function(f, btn) {
_filter = f;
document.querySelectorAll('.ct-fbtn').forEach(function(b){ b.classList.remove('act'); });
if (btn) btn.classList.add('act');
render();
};
window.ctSort = function(field, th) {
if (_sortField === field) { _sortDir *= -1; }
else { _sortField = field; _sortDir = 1; }
document.querySelectorAll('.ct-table thead th').forEach(function(h){ h.classList.remove('sort-asc','sort-desc'); });
if (th) th.classList.add(_sortDir === 1 ? 'sort-asc' : 'sort-desc');
render();
};
window._openRealCase = function(i) {
var c = (_getContracts() || [])[i];
if (!c) return;
toast('📄 ' + (c.type || 'Договор') + (c.counterparty ? ' · ' + c.counterparty : ''));
};
// Рендер при открытии вкладки cases и cabinet
var _origTab2 = window.tab;
window.tab = function(id) {
if (_origTab2) _origTab2(id);
if (id === 'cases') setTimeout(render, 50);
};
var _origGo2 = window.go;
window.go = function(id) {
if (_origGo2) _origGo2(id);
if (id === 'cabinet') {
setTimeout(render, 80);
if (typeof _initOrgUser === 'function') setTimeout(_initOrgUser, 120);
}
};
window.addEventListener('DOMContentLoaded', function(){
var el = document.getElementById('ct-tbody');
if (el) render();
});
})();
/* ── ELENA INTAKE v2 ── */
// elenaIntentGo — вызывается с карточек: скрывает el-step1, продолжает в chatwrap
function elenaIntentGo(intent) {
// Скрываем весь el-step1 (приветствие + карточки)
var step1 = document.getElementById('el-step1');
if (step1) step1.style.display = 'none';
// Продолжаем разговор в chatwrap
elenaIntent(intent);
}
function elenaIntentFromInput() {
var txt = (document.getElementById('intake-custom').value || '').trim();
if (!txt) { document.getElementById('intake-custom').focus(); return; }
var t = txt.toLowerCase();
var intent = 'question';
if (/доверенност/.test(t)) { intent = 'power'; }
else if (/протокол|разногласи|убрат|невыгод/.test(t)) { intent = 'dispute'; }
else if (/претензи|жалоб/.test(t)) { intent = 'check'; }
else if (/состав|написат|создат|подготов/.test(t) && !/проверит|анализ/.test(t)) { intent = 'create'; }
else if (/проверит|анализ|посмотр|риск/.test(t)) { intent = 'check'; }
else if (/договор|контракт|соглашени/.test(t)) { intent = 'check'; }
else if (/кабинет|войт|мои дел/.test(t)) { intent = 'cabinet'; }
elenaIntent(intent);
}
function elenaIntent(intent) {
// cabinet — сразу в кабинет
if (intent === 'cabinet') { go('cabinet'); return; }
// question — скрываем el-step1, показываем поле ввода в chatwrap
if (intent === 'question') {
var step1El = document.getElementById('el-step1');
if (step1El) step1El.style.display = 'none';
var wrap = document.querySelector('.chatwrap');
if (wrap) {
var qBox = document.createElement('div');
qBox.className = 'msg';
qBox.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'Спрашивайте — отвечу по нормам ГК/ТК/ЗоЗПП. Или включите микрофон 🎙</div>';
wrap.appendChild(qBox);
}
_elenaShowInput();
return;
}
// create — Елена начинает разговор, НЕ показывает пикер
// Пикер (4 карточки) появляется только если тип документа неизвестен и пользователь уже ответил
if (intent === 'create') {
var step1c = document.getElementById('el-step1');
if (step1c) step1c.style.display = 'none';
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
// Добавляем сообщение Елены + поле ввода
var msgDiv = document.createElement('div');
msgDiv.className = 'msg';
msgDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'Составлю документ. Расскажите коротко: кто стороны и о чём договариваетесь?' +
'<div style="color:var(--mut);font-size:12px;margin-top:4px">Например: «ИП оказывает услуги дизайна физлицу» или «аренда офиса между двумя ООО»</div>' +
'</div></div>';
wrap.appendChild(msgDiv);
_elenaShowInput();
// Меняем placeholder на нужный
setTimeout(function(){
var inp = document.getElementById('intake-custom-cont');
if (inp) inp.placeholder = 'Опишите кто стороны и о чём договор...';
}, 100);
return;
}
// _LEGACY_PICKER — показывается только при явном вызове (устаревший путь)
if (intent === 'create_picker') {
var step1x = document.getElementById('el-step1');
if (step1x) step1x.style.display = 'none';
var wrapx = document.querySelector('.chatwrap');
var old = document.getElementById('el-create-picker'); if (old) old.remove();
var div = document.createElement('div');
div.id = 'el-create-picker';
div.innerHTML =
'<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>Хорошо! Какой документ нужен?</div></div>' +
'<div class="doc-type-grid">' +
'<div class="doc-type-card" onclick="elenaCreate(\'contract\')">' +
'<div class="dt-ico">📄</div><div class="dt-lbl">Договор</div>' +
'<div class="dt-sub">купли-продажи, аренды, услуг, подряда</div></div>' +
'<div class="doc-type-card" onclick="elenaCreate(\'power\')">' +
'<div class="dt-ico">📑</div><div class="dt-lbl">Доверенность</div>' +
'<div class="dt-sub">генеральная, на авто, в суд</div></div>' +
'<div class="doc-type-card" onclick="elenaCreate(\'claim\')">' +
'<div class="dt-ico">📋</div><div class="dt-lbl">Претензия</div>' +
'<div class="dt-sub">к контрагенту, продавцу, банку</div></div>' +
'<div class="doc-type-card" onclick="elenaCreate(\'other\')">' +
'<div class="dt-ico">✏️</div><div class="dt-lbl">Другое</div>' +
'<div class="dt-sub">расписка, соглашение, акт, заявление</div></div>' +
'</div>';
if (wrapx) { wrapx.appendChild(div); div.scrollIntoView({behavior:'smooth'}); }
return;
}
// power — карточка услуги с ценой и CTA (не upload-форма!)
if (intent === 'power') {
document.querySelectorAll('#el-step1, #el-step-create').forEach(function(el){ el.style.display='none'; });
var wrap = document.querySelector('.chatwrap');
var old = document.getElementById('el-service-power');
if (old) old.remove();
var div = document.createElement('div');
div.id = 'el-service-power';
div.innerHTML =
'<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>Составлю доверенность — нотариально или в простой письменной форме. Обычно готово за 15 минут после уточнения деталей.</div></div>' +
'<div class="svc-order-card">' +
'<div class="svc-ico">📑</div>' +
'<div class="svc-name">Доверенность</div>' +
'<div class="svc-desc">Разовая или генеральная · на авто, недвижимость, суд, получение документов</div>' +
'<div class="svc-price">от 990 ₽ <span class="svc-time">· готово за 15 мин</span></div>' +
'<div class="svc-actions">' +
'<button class="btn btn-p" style="padding:8px 18px;font-size:13px" onclick="go(\'pay\')">🛒 Заказать и оплатить</button>' +
'<button class="svc-btn-detail" onclick="_showSvcDetail(this)">💬 Описать детали</button>' +
'</div>' +
'</div>';
if (wrap) { wrap.appendChild(div); div.scrollIntoView({behavior:'smooth'}); }
return;
}
// check / dispute — сначала Елена отвечает, потом мягко предлагает загрузить
var userTxt = (document.getElementById('intake-custom') || {}).value || '';
document.querySelectorAll('#el-step1, #el-step-create').forEach(function(el){ el.style.display='none'; });
var wrap = document.querySelector('.chatwrap');
if (!wrap) {
// fallback: сразу upload
var upload = document.getElementById('el-step-upload');
if(upload) upload.style.display='';
return;
}
// Пузырь пользователя
if (userTxt) {
var ub = document.createElement('div');
ub.className = 'msg user-msg';
ub.innerHTML = '<div class="bubble">' + userTxt.replace(/</g,'&lt;') + '</div>';
wrap.appendChild(ub);
}
// Typing → API → ответ → кнопка загрузки
_hcAddTyping();
_elenaApi(userTxt || intent, intent, function(apiReply) {
_hcRemoveTyping();
var defaultReply = intent === 'dispute'
? 'Понял ситуацию — разберёмся. Если есть текст договора, загрузите его — сразу покажу спорные пункты и как их оспорить.'
: 'Хорошо, посмотрю. Если у вас есть текст договора — загрузите или вставьте, найду все риски и объясню понятно.';
var replyText = apiReply || defaultReply;
// Добавляем ответ Елены — кнопка загрузки встроена в пузырь если нужна
var hasContract = ((document.getElementById('el-paste') || {}).value || '').length > 50;
var uploadBtn = (intent === 'check' && !hasContract)
? '<div style="margin-top:12px">'
+ '<button class="btn btn-p" style="padding:7px 16px;font-size:13px" onclick="'
+ 'var up=document.getElementById(\'el-step-upload\');if(up){up.style.display=\'\';up.scrollIntoView({behavior:\'smooth\'});}">'
+ '📂 Загрузить договор</button></div>'
: '';
var mb = document.createElement('div');
mb.className = 'msg';
mb.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>'
+ '<div class="bubble"><div class="nm">Елена</div>' + replyText + uploadBtn + '</div>';
wrap.appendChild(mb);
mb.scrollIntoView({behavior:'smooth'});
});
}
function elenaCreate(type) {
var msgs = {
contract: 'Договор — отличный выбор. Уточните: какой тип и между кем? Например: «договор оказания услуг между ИП и физлицом». Чем точнее — тем лучше результат.',
power: 'Доверенность готовлю быстро. Укажите: кто доверяет, кому, и что именно разрешает делать (продать авто, представлять в суде, получить документы)?',
claim: 'Претензию составлю под вашу ситуацию. Опишите: к кому претензия, что нарушили и какой результат хотите (возврат денег, замена товара, неустойка)?',
other: 'Расскажите что нужно составить — постараюсь помочь или подберу ближайший шаблон.',
};
var msg = msgs[type] || msgs.other;
// Скрываем пикер типа документа
var picker = document.getElementById('el-create-picker');
if (picker) picker.style.display = 'none';
var stepCreate = document.getElementById('el-step-create');
if (stepCreate) stepCreate.style.display = 'none';
// Показываем ответ Елены и поле ввода
var wrap = document.querySelector('.chatwrap');
var div = document.createElement('div');
div.innerHTML = '<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div><div class="bubble"><div class="nm">Елена</div>' + msg + '</div></div>' +
'<div class="voice-input-row" style="margin-left:0">' +
'<input id="create-input" placeholder="Опишите детали…" style="flex:1;border:1.5px solid var(--line);border-radius:11px;padding:11px 14px;font-size:14px;font-family:inherit;outline:none">' +
'<button class="voice-btn" onclick="toggleVoice(\'create-input\')" title="Голосовой ввод">🎤</button>' +
'<button class="send-btn" onclick="toast(\'✍️ Принято — начинаю составлять документ по вашему запросу\')">→</button>' +
'</div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
/* ── DEADLINES ── */
// Структура: { id, caseId, caseName, title, type, date (YYYY-MM-DD),
// quote, done }
// В проде: заполняется при анализе договора через Claude API
// Промпт парсинга (см. архитектуру ниже):
// "Извлеки все сроки из договора: даты оплат, уведомлений, пролонгации,
// расторжения, штрафных триггеров. Формат: [{title, type, date, quote}]"
// Дедлайны загружаются из localStorage (сохраняются после анализа договора).
// Демо-данные НЕ показываются реальным пользователям.
var _DEADLINES = (function() {
try {
var saved = localStorage.getItem('zashita_deadlines');
if (saved) return JSON.parse(saved);
} catch(e) {}
return []; // новый пользователь — пустой список
})();
var _dlFilter = 'all';
var _dlDone = new Set();
function _dlStatus(dateStr) {
var today = new Date(); today.setHours(0,0,0,0);
var d = new Date(dateStr); d.setHours(0,0,0,0);
var diff = Math.round((d - today) / 86400000);
if (diff < 0) return { cls:'overdue', badge:'Просрочено ' + Math.abs(diff) + ' дн.', days: diff };
if (diff <= 7) return { cls:'soon', badge:'Через ' + diff + ' дн.', days: diff };
return { cls:'ok', badge:'Через ' + diff + ' дн.', days: diff };
}
function _fmtDate(dateStr) {
var m = ['янв','фев','мар','апр','мая','июн','июл','авг','сен','окт','ноя','дек'];
var p = dateStr.split('-');
return p[2].replace(/^0/,'') + ' ' + m[parseInt(p[1])-1] + ' ' + p[0];
}
// ── ШАБЛОНЫ — контекст + генерация ──────────────────────────────────────────
var _TEMPLATE_META = {
notice_no_renewal: { icon:'📬', name:'Уведомление о непродлении', triggers:['аренда','непродлени','уведомит'] },
claim_payment: { icon:'✉️', name:'Претензия об оплате', triggers:['не платит','долг','оплат','задолженн'] },
claim_quality: { icon:'⚠️', name:'Претензия о качестве', triggers:['качеств','брак','дефект','неисправ'] },
deposit_return: { icon:'💰', name:'Требование о возврате депозита', triggers:['депозит','обеспечительн'] },
termination_agreement: { icon:'🚪', name:'Соглашение о расторжении', triggers:['расторг','выйти'] },
};
// Рендерит контекстные шаблоны при открытии вкладки
function renderContextTemplates() {
var el = document.getElementById('shab-context');
if (!el) return;
var suggested = [];
// 1. Из активных дедлайнов
(_DEADLINES || []).forEach(function(d) {
if (d.done) return;
var t = (d.title || '').toLowerCase();
Object.keys(_TEMPLATE_META).forEach(function(key) {
var meta = _TEMPLATE_META[key];
if (meta.triggers.some(function(tr){ return t.includes(tr); })) {
var st = _dlStatus(d.date);
if (st.days <= 14) { // только срочные
suggested.push({ key: key, source: 'deadline', dl: d, urgency: st });
}
}
});
});
// 2. Из истории чата
var history = _chatHistory.slice(-20);
var histText = history.map(function(m){ return m.content || ''; }).join(' ').toLowerCase();
Object.keys(_TEMPLATE_META).forEach(function(key) {
var meta = _TEMPLATE_META[key];
var alreadyIn = suggested.some(function(s){ return s.key === key; });
if (!alreadyIn && meta.triggers.some(function(tr){ return histText.includes(tr); })) {
suggested.push({ key: key, source: 'chat' });
}
});
if (!suggested.length) { el.innerHTML = ''; return; }
var html = '<div class="elena-q-lbl" style="margin-bottom:10px">💡 Рекомендую для вашей ситуации:</div>' +
'<div class="tpls">';
suggested.slice(0, 3).forEach(function(s) {
var meta = _TEMPLATE_META[s.key];
var badge = '';
if (s.source === 'deadline' && s.urgency) {
var cls = s.urgency.cls === 'overdue' ? 'crit' : s.urgency.cls === 'soon' ? 'warn' : 'ok';
badge = '<span class="risk-badge ' + cls + '" style="font-size:10px;margin-left:6px">' +
s.urgency.badge + '</span>';
} else if (s.source === 'chat') {
badge = '<span style="font-size:10px;color:var(--mut);margin-left:6px">из разговора</span>';
}
html += '<div class="tpl" style="border-color:var(--bg)" onclick="_startTemplate(\'' + s.key + '\')">' +
'<div class="ti">' + meta.icon + '</div>' +
'<div class="tn">' + meta.name + badge + '</div>' +
'<div class="td">Нажмите — Елена уточнит и заполнит</div>' +
'</div>';
});
html += '</div>';
el.innerHTML = html;
}
// Запускает флоу: уточнение → генерация
var _tplCurrent = null; // текущий шаблон в процессе заполнения
function _startTemplate(templateKey) {
var meta = _TEMPLATE_META[templateKey] || { name: templateKey, icon: '📄' };
_tplCurrent = { key: templateKey, parties: {}, contract_data: {}, extra: '' };
// Собираем что уже знаем
var contracts = _getContracts();
var ctx = _buildElenaContext();
var postalData = typeof _postalData !== 'undefined' ? _postalData : null;
// Пытаемся предзаполнить из данных клиента
if (postalData && postalData.sender) {
_tplCurrent.parties.from_name = postalData.sender;
_tplCurrent.parties.from_addr = postalData.senderAddr;
_tplCurrent.parties.to_name = postalData.counterparty;
_tplCurrent.parties.to_addr = postalData.counterAddr;
} else {
var b2b = null;
try { b2b = JSON.parse(localStorage.getItem('zashita_b2b') || 'null'); } catch(e){}
if (b2b && b2b.name) _tplCurrent.parties.from_name = b2b.name;
}
// Данные из последнего договора
if (contracts.length) {
var c = contracts[0];
_tplCurrent.contract_data.type = c.type;
_tplCurrent.contract_data.counterparty = c.counterparty;
if (c.end) _tplCurrent.contract_data.end_date = c.end;
}
// Показываем модальный флоу уточнения
_showTemplateClarify(meta);
}
function _showTemplateClarify(meta) {
var old = document.getElementById('tpl-modal'); if (old) old.remove();
// Определяем какой вопрос задаём
var fromName = _tplCurrent.parties.from_name || '';
var toName = _tplCurrent.parties.to_name || _tplCurrent.contract_data.counterparty || '';
var question = '';
if (fromName && toName) {
question = 'Документ от <b>' + fromName + '</b> → <b>' + toName + '</b>. Всё верно?';
} else if (fromName) {
question = 'От вас (<b>' + fromName + '</b>). Кому адресуем? Укажите получателя.';
} else {
question = 'Кто отправитель и получатель документа?';
}
var modal = document.createElement('div');
modal.id = 'tpl-modal';
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:flex-end;justify-content:center';
modal.innerHTML =
'<div style="background:#fff;border-radius:18px 18px 0 0;width:100%;max-width:660px;padding:24px;max-height:85vh;overflow-y:auto">' +
'<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px">' +
'<img src="logos/elena-photo.jpg" style="width:36px;height:36px;border-radius:50%">' +
'<div>' +
'<div style="font-weight:700;font-size:14px">Елена · ' + (meta.icon||'📄') + ' ' + (meta.name||'Документ') + '</div>' +
'<div style="font-size:12px;color:var(--mut)">Уточню данные и заполню за секунды</div>' +
'</div>' +
'<button onclick="document.getElementById(\'tpl-modal\').remove()" style="margin-left:auto;background:none;border:none;font-size:20px;cursor:pointer;color:var(--mut)">✕</button>' +
'</div>' +
'<div style="font-size:14px;margin-bottom:14px">' + question + '</div>' +
'<div style="display:flex;flex-direction:column;gap:10px">' +
'<input id="tpl-from" class="elena-main-inp" placeholder="Отправитель (ваше ФИО / название компании)" value="' + (fromName||'') + '">' +
'<input id="tpl-to" class="elena-main-inp" placeholder="Получатель (ФИО / название контрагента)" value="' + (toName||'') + '">' +
'<input id="tpl-extra" class="elena-main-inp" placeholder="Дополнительно: номер договора, сумма, дата... (необязательно)">' +
'</div>' +
'<div style="margin-top:16px;display:flex;gap:10px">' +
'<button class="btn btn-p" style="flex:1;padding:11px" onclick="_generateFromModal()">✨ Заполнить автоматически</button>' +
'<button class="btn btn-o" style="padding:11px 16px" onclick="document.getElementById(\'tpl-modal\').remove()">Отмена</button>' +
'</div>' +
'</div>';
document.body.appendChild(modal);
setTimeout(function(){
var inp = document.getElementById('tpl-from');
if (inp && !inp.value) inp.focus();
else { var t = document.getElementById('tpl-to'); if (t && !t.value) t.focus(); }
}, 200);
}
function _generateFromModal() {
if (!_tplCurrent) return;
var fromVal = (document.getElementById('tpl-from') || {}).value || '';
var toVal = (document.getElementById('tpl-to') || {}).value || '';
var extraVal = (document.getElementById('tpl-extra') || {}).value || '';
if (!fromVal && !toVal) { toast('Укажите хотя бы одну из сторон'); return; }
_tplCurrent.parties.from_name = fromVal;
_tplCurrent.parties.to_name = toVal;
_tplCurrent.extra = extraVal;
// Закрываем модал, показываем progress
var modal = document.getElementById('tpl-modal');
if (modal) modal.innerHTML =
'<div style="background:#fff;border-radius:18px 18px 0 0;width:100%;max-width:660px;padding:40px;text-align:center">' +
'<img src="logos/elena-photo.jpg" style="width:48px;height:48px;border-radius:50%;margin-bottom:12px">' +
'<div style="font-weight:700;margin-bottom:6px">Елена заполняет документ…</div>' +
'<div style="color:var(--mut);font-size:13px">Обычно занимает 5-10 секунд</div>' +
'</div>';
fetch(API_BASE + '/api/generate', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
template: _tplCurrent.key,
parties: _tplCurrent.parties,
contract_data: _tplCurrent.contract_data,
extra: _tplCurrent.extra
})
})
.then(function(r){ return r.json(); })
.then(function(data){
var m = document.getElementById('tpl-modal'); if (m) m.remove();
if (data.error) { toast('Ошибка: ' + data.error); return; }
_showGeneratedDoc(data);
})
.catch(function(e){
var m = document.getElementById('tpl-modal'); if (m) m.remove();
toast('Ошибка генерации: ' + e.message);
});
}
// Режимы документа: view | elena-add | blocks | direct-edit
var _docMode = 'view';
var _docData = null; // текущий документ
function _showGeneratedDoc(data) {
_docData = data;
_docMode = 'view';
var old = document.getElementById('tpl-result'); if (old) old.remove();
_renderDocModal();
_updateDossier({ decisions: ['Составлен документ: ' + (data.title||data.template)] });
}
function _renderDocModal() {
var old = document.getElementById('tpl-result'); if (old) old.remove();
var data = _docData; if (!data) return;
// Готовые блоки для добавления по типу документа
var BLOCKS = {
notice_no_renewal: [
'Прошу подтвердить получение настоящего уведомления в письменной форме',
'Прошу организовать передачу помещения по акту в согласованную дату',
'Дополнительно уведомляю о намерении истребовать обеспечительный платёж'
],
claim_payment: [
'В случае неоплаты оставляю за собой право начислить пени по ст. 395 ГК РФ',
'Прошу подтвердить получение настоящей претензии',
'При урегулировании в досудебном порядке готов рассмотреть рассрочку'
],
act_acceptance: [
'Замечания и недостатки, выявленные при приёмке, зафиксированы в Приложении №1',
'Гарантийный срок исчисляется с даты подписания настоящего акта',
'Акт составлен в двух экземплярах, имеющих равную юридическую силу'
]
};
var tplKey = (_tplCurrent && _tplCurrent.key) || '';
var blocks = BLOCKS[tplKey] || [
'Прошу подтвердить получение настоящего документа',
'Стороны вправе согласовать иные условия дополнительным соглашением',
'Настоящий документ составлен в электронном виде и имеет юридическую силу'
];
var isEdit = (_docMode === 'direct-edit');
var modal = document.createElement('div');
modal.id = 'tpl-result';
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;padding:16px';
modal.innerHTML =
'<div style="background:#fff;border-radius:16px;width:100%;max-width:720px;max-height:92vh;display:flex;flex-direction:column">' +
// ── Заголовок
'<div style="padding:14px 20px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px">' +
'<div style="flex:1">' +
'<div style="font-weight:700;font-size:15px">' + (data.title||'Документ') + '</div>' +
'<div style="font-size:12px;color:var(--mut)">' + (data.date||'') +
(isEdit ? ' · <span style="color:#d97706;font-weight:600">режим редактирования</span>' : '') +
'</div>' +
'</div>' +
'<button onclick="document.getElementById(\'tpl-result\').remove()" style="background:none;border:none;font-size:20px;cursor:pointer;color:var(--mut)">✕</button>' +
'</div>' +
// ── Режим редактирования — дисклеймер в UI (не в документе)
(isEdit ?
'<div style="padding:10px 20px;background:#fffbeb;border-bottom:1px solid #fcd34d;font-size:12px;color:#92400e">' +
'<strong>Вы редактируете документ самостоятельно.</strong> ЗАЩИТА является информационным сервисом — ' +
'ответственность за внесённые изменения несёте вы. Документ отправляется контрагенту без этой пометки.' +
'</div>' : '') +
// ── Тулбар режимов
'<div style="padding:8px 20px;border-bottom:1px solid var(--line);display:flex;gap:6px;background:#fafafa">' +
'<button class="svc-btn-detail" style="font-size:11px;' + (_docMode==='view'?'border-color:var(--bg);color:var(--bg)':'') + '" ' +
'onclick="_setDocMode(\'view\')">Просмотр</button>' +
'<button class="svc-btn-detail" style="font-size:11px;' + (_docMode==='elena-add'?'border-color:var(--bg);color:var(--bg)':'') + '" ' +
'onclick="_setDocMode(\'elena-add\')">Елена добавит</button>' +
'<button class="svc-btn-detail" style="font-size:11px;' + (_docMode==='blocks'?'border-color:var(--bg);color:var(--bg)':'') + '" ' +
'onclick="_setDocMode(\'blocks\')">Готовые блоки</button>' +
'<button class="svc-btn-detail" style="font-size:11px;' + (_docMode==='direct-edit'?'border-color:#d97706;color:#d97706':'') + '" ' +
'onclick="_setDocMode(\'direct-edit\')">Редактировать</button>' +
'</div>' +
// ── Панель режима
'<div id="doc-mode-panel" style="padding:0 20px">' + _getModePanelHTML(blocks) + '</div>' +
// ── Текст документа
'<div style="padding:0 20px 8px;overflow-y:auto;flex:1;margin-top:8px">' +
(isEdit
? '<textarea id="tpl-doc-text" style="width:100%;min-height:320px;border:1.5px solid #d97706;border-radius:10px;' +
'padding:14px;font-family:inherit;font-size:13px;line-height:1.7;color:#1a1a2e;resize:vertical">' +
(data.text||'').replace(/</g,'&lt;') + '</textarea>'
: '<pre id="tpl-doc-text" style="white-space:pre-wrap;font-family:inherit;font-size:13px;line-height:1.7;color:#1a1a2e;' +
'border:1.5px solid var(--line);border-radius:10px;padding:14px">' +
(data.text||'').replace(/</g,'&lt;') + '</pre>') +
'</div>' +
// ── Нижняя панель действий
'<div style="padding:12px 20px;border-top:1px solid var(--line);display:flex;gap:8px;flex-wrap:wrap">' +
(isEdit
? '<button class="btn btn-p" style="padding:8px 16px;font-size:13px" onclick="_saveDocEdits()">✅ Сохранить изменения</button>'
: '<button class="btn btn-p" style="padding:8px 16px;font-size:13px" onclick="_printDoc()">Распечатать</button>') +
'<button class="btn btn-o" style="padding:8px 16px;font-size:13px" onclick="_sendDocToCounterparty()">Отправить контрагенту</button>' +
'<button class="svc-btn-detail" style="font-size:12px" onclick="_copyDoc()">Скопировать</button>' +
'<button class="svc-btn-detail" style="font-size:12px" onclick="document.getElementById(\'tpl-result\').remove();tab(\'casemap\');go(\'cabinet\')">Карта дела</button>' +
'</div>' +
'</div>';
document.body.appendChild(modal);
}
function _getModePanelHTML(blocks) {
if (_docMode === 'elena-add') {
return '<div style="padding:12px 0;display:flex;gap:8px;align-items:flex-start">' +
'<input id="elena-add-inp" class="elena-main-inp" placeholder="Опишите что добавить: «добавь пункт об ответственности за просрочку»" style="flex:1;font-size:13px">' +
'<button class="btn btn-p" style="padding:9px 14px;font-size:13px;white-space:nowrap" onclick="_elenaAddToDoc()">Добавить</button>' +
'</div>';
}
if (_docMode === 'blocks') {
return '<div style="padding:10px 0;display:flex;flex-direction:column;gap:6px">' +
'<div style="font-size:12px;color:var(--mut);margin-bottom:2px">Выберите готовый блок для добавления в документ:</div>' +
blocks.map(function(b) {
return '<button style="text-align:left;padding:8px 12px;border:1.5px solid var(--line);border-radius:8px;' +
'background:#fafafa;cursor:pointer;font-size:12px;font-family:inherit;transition:border-color .15s" ' +
'onmouseover="this.style.borderColor=\'var(--bg)\'" onmouseout="this.style.borderColor=\'var(--line)\'" ' +
'onclick="_addBlockToDoc(\'' + b.replace(/'/g, "\\'") + '\')">' +
'+ ' + b + '</button>';
}).join('') +
'</div>';
}
return ''; // view и direct-edit — без панели
}
function _setDocMode(mode) {
_docMode = mode;
_renderDocModal();
// Скроллим к тексту
setTimeout(function(){
var el = document.getElementById('tpl-doc-text');
if (el) el.scrollIntoView({behavior:'smooth', block:'nearest'});
}, 100);
}
// Режим 1: Елена добавляет через API
function _elenaAddToDoc() {
var inp = document.getElementById('elena-add-inp');
if (!inp || !inp.value.trim()) return;
var request = inp.value.trim();
inp.value = '';
inp.placeholder = 'Елена думает...';
inp.disabled = true;
var currentText = '';
var el = document.getElementById('tpl-doc-text');
if (el) currentText = el.tagName === 'TEXTAREA' ? el.value : (el.textContent||el.innerText||'');
fetch(API_BASE + '/api/generate', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({
template: (_tplCurrent && _tplCurrent.key) || 'custom',
parties: (_tplCurrent && _tplCurrent.parties) || {},
contract_data: (_tplCurrent && _tplCurrent.contract_data) || {},
extra: 'ТЕКУЩИЙ ДОКУМЕНТ:\n' + currentText.slice(0, 2000) + '\n\nЗАПРОС КЛИЕНТА: ' + request +
'\n\nДобавь нужный фрагмент в конец документа в том же стиле. Верни ТОЛЬКО дополнение (не весь документ).'
})
})
.then(function(r){ return r.json(); })
.then(function(d) {
inp.disabled = false;
inp.placeholder = 'Опишите что добавить...';
if (d.error) { toast('Ошибка: ' + d.error); return; }
var addition = '\n\n' + (d.text || '');
// Добавляем к тексту
var textEl = document.getElementById('tpl-doc-text');
if (textEl) {
if (textEl.tagName === 'TEXTAREA') textEl.value += addition;
else textEl.textContent += addition;
}
if (_docData) _docData.text = (_docData.text || '') + addition;
toast('✅ Елена добавила пункт');
_addCaseNote('decision', 'Документ дополнен: ' + request, {});
})
.catch(function() { inp.disabled = false; toast('Ошибка'); });
}
// Режим 2: Готовый блок
function _addBlockToDoc(blockText) {
var textEl = document.getElementById('tpl-doc-text');
var addition = '\n\n' + blockText + '.';
if (textEl) {
if (textEl.tagName === 'TEXTAREA') textEl.value += addition;
else textEl.textContent += addition;
}
if (_docData) _docData.text = (_docData.text || '') + addition;
toast('✅ Блок добавлен');
_setDocMode('view'); // возвращаемся в просмотр
}
// Режим 3: Сохранить прямое редактирование
function _saveDocEdits() {
var textEl = document.getElementById('tpl-doc-text');
if (textEl && _docData) {
_docData.text = textEl.tagName === 'TEXTAREA' ? textEl.value : (textEl.textContent||'');
_addCaseNote('decision', 'Документ отредактирован клиентом самостоятельно', { ts: new Date().toISOString() });
}
_setDocMode('view');
toast('✅ Изменения сохранены');
}
function _printDoc() {
var text = document.getElementById('tpl-doc-text');
if (!text) return;
var w = window.open('', '_blank');
w.document.write('<html><head><title>Документ</title>' +
'<style>body{font-family:Arial,sans-serif;font-size:13px;line-height:1.7;padding:40px;max-width:700px;margin:0 auto}' +
'pre{white-space:pre-wrap;font-family:inherit}</style></head>' +
'<body><pre>' + text.innerHTML + '</pre>' +
'<script>window.print();<\/script></body></html>');
w.document.close();
}
function _sendDocToCounterparty() {
var textEl = document.getElementById('tpl-doc-text');
if (!textEl) return;
var text = textEl.textContent || textEl.innerText || '';
var postalData = typeof _postalData !== 'undefined' ? _postalData : null;
var toEmail = (postalData && postalData.counterEmail) || '';
var toName = (postalData && postalData.counterparty) || 'Контрагенту';
// Закрываем модал документа
var modal = document.getElementById('tpl-result');
if (modal) modal.remove();
// Показываем модал отправки
var sendModal = document.createElement('div');
sendModal.id = 'send-doc-modal';
sendModal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1000;display:flex;align-items:flex-end;justify-content:center';
sendModal.innerHTML =
'<div style="background:#fff;border-radius:18px 18px 0 0;width:100%;max-width:540px;padding:24px">' +
'<div style="font-weight:700;font-size:15px;margin-bottom:6px">📨 Отправить контрагенту</div>' +
'<div style="font-size:13px;color:var(--mut);margin-bottom:16px">Документ будет отправлен по email или вы скопируете ссылку</div>' +
'<div style="display:flex;flex-direction:column;gap:10px;margin-bottom:16px">' +
'<input id="send-email" class="elena-main-inp" type="email" placeholder="Email контрагента" value="' + toEmail + '">' +
'<input id="send-subject" class="elena-main-inp" placeholder="Тема письма" value="Направляю документ для ознакомления">' +
'</div>' +
'<div style="display:flex;gap:8px;flex-wrap:wrap">' +
'<button class="btn btn-p" style="flex:1;padding:10px" onclick="_doSendEmail()">📧 Отправить по email</button>' +
'<button class="btn btn-o" style="padding:10px 14px" onclick="_copyAndClose()">📋 Скопировать + закрыть</button>' +
'<button class="svc-btn-detail" onclick="document.getElementById(\'send-doc-modal\').remove()">Отмена</button>' +
'</div>' +
'<div style="margin-top:12px;font-size:11px;color:var(--mut)">' +
'💡 После отправки зафиксируем факт в карте дела — дата, получатель.' +
'</div>' +
'</div>';
document.body.appendChild(sendModal);
setTimeout(function(){ var e = document.getElementById('send-email'); if (e && !e.value) e.focus(); }, 200);
}
function _doSendEmail() {
var email = (document.getElementById('send-email') || {}).value || '';
var subject = (document.getElementById('send-subject') || {}).value || 'Документ';
var textEl = document.getElementById('tpl-doc-text');
var body = textEl ? (textEl.textContent || '') : '';
// Открываем mailto (браузер откроет почтовый клиент)
var mailtoLink = 'mailto:' + encodeURIComponent(email) +
'?subject=' + encodeURIComponent(subject) +
'&body=' + encodeURIComponent(body.slice(0, 2000) + (body.length > 2000 ? '\n\n...(полный текст скопируйте из приложения)' : ''));
window.location.href = mailtoLink;
// Фиксируем в карте дела
_addCaseNote('decision',
'Документ отправлен: ' + subject + (email ? ' → ' + email : ''),
{ email: email, date: new Date().toISOString() });
document.getElementById('send-doc-modal').remove();
toast('✅ Открываю почтовый клиент. Факт отправки зафиксирован в карте дела.');
}
function _copyAndClose() {
_copyDoc();
var m = document.getElementById('send-doc-modal');
if (m) m.remove();
_addCaseNote('decision', 'Текст документа скопирован для отправки контрагенту', {});
toast('📋 Текст скопирован. Вставьте в письмо контрагенту.');
}
function _copyDoc() {
var text = document.getElementById('tpl-doc-text');
if (!text) return;
navigator.clipboard.writeText(text.textContent || text.innerText)
.then(function(){ toast('✅ Скопировано в буфер'); })
.catch(function(){ toast('Скопируйте текст вручную'); });
}
function renderDeadlines() {
var list = document.getElementById('dl-list');
var sumEl = document.getElementById('dl-summary');
if (!list) return;
var items = _DEADLINES.map(function(d) {
var isDone = _dlDone.has(d.id) || d.done;
var st = isDone ? {cls:'done', badge:'Выполнено', days:999} : _dlStatus(d.date);
return Object.assign({}, d, {isDone: isDone, st: st});
});
// Сводка
var nOver = items.filter(function(i){ return i.st.cls==='overdue'; }).length;
var nSoon = items.filter(function(i){ return i.st.cls==='soon'; }).length;
var nOk = items.filter(function(i){ return i.st.cls==='ok'; }).length;
if (sumEl) sumEl.innerHTML =
'<div class="dl-sum-card red"><div class="dl-sum-num">'+nOver+'</div><div class="dl-sum-lbl">Просрочено</div></div>' +
'<div class="dl-sum-card yellow"><div class="dl-sum-num">'+nSoon+'</div><div class="dl-sum-lbl">Скоро</div></div>' +
'<div class="dl-sum-card green"><div class="dl-sum-num">'+nOk+'</div><div class="dl-sum-lbl">В срок</div></div>';
// Фильтрация
var filtered = items.filter(function(i) {
if (_dlFilter === 'done') return i.isDone;
if (_dlFilter === 'urgent') return !i.isDone && (i.st.cls==='overdue'||i.st.cls==='soon');
return true;
});
// Сортировка: overdue → soon → ok → done
filtered.sort(function(a,b){ return a.st.days - b.st.days; });
if (!filtered.length) {
list.innerHTML = '<div class="dl-empty"><div class="dl-empty-ico">✅</div><div class="dl-empty-txt">Активных сроков нет</div><div class="dl-empty-sub">Елена найдёт сроки автоматически при загрузке договора</div></div>';
return;
}
list.innerHTML = filtered.map(function(d) {
var btnHtml = d.isDone
? '<button class="dl-btn done-btn" disabled>✅ Выполнено</button>'
: '<button class="dl-btn" onclick="markDeadlineDone('+d.id+')">Отметить выполненным</button>';
return '<div class="dl-card '+d.st.cls+'">' +
'<div class="dl-dot"></div>' +
'<div class="dl-body">' +
'<div class="dl-title">'+d.title+'</div>' +
'<div class="dl-meta">'+d.caseName+' · '+d.type+'</div>' +
'<div class="dl-quote">'+d.quote+'</div>' +
btnHtml +
'</div>' +
'<div class="dl-right">' +
'<div class="dl-date">'+_fmtDate(d.date)+'</div>' +
'<div class="dl-badge '+d.st.cls+'">'+d.st.badge+'</div>' +
'</div>' +
'</div>';
}).join('');
}
function dlFilter(name, el) {
_dlFilter = name;
document.querySelectorAll('.dl-ftab').forEach(function(t){ t.classList.remove('on'); });
if (el) el.classList.add('on');
renderDeadlines();
}
function markDeadlineDone(id) {
_dlDone.add(id);
renderDeadlines();
toast('✅ Срок отмечен выполненным');
}
// Добавить дедлайн из анализа договора (вызывается при парсинге)
function addDeadlinesFromContract(caseId, caseName, deadlines) {
deadlines.forEach(function(d) {
var maxId = Math.max.apply(null, _DEADLINES.map(function(x){return x.id;})) || 0;
_DEADLINES.push({ id: maxId+1, caseId: caseId, caseName: caseName,
title: d.title, type: d.type, date: d.date, quote: d.quote||'', done: false });
});
if (document.getElementById('dl-list')) renderDeadlines();
}
window.addEventListener('DOMContentLoaded', function() {
if (document.getElementById('dl-list')) renderDeadlines();
});
/* ── PAY HYBRID ── */
var _payMode = 'once'; // once | sub
var _selPkg = 2; // selected package id
var _selSub = 2; // selected sub id
var _selPkgPrice = 490;
var _selSubPrice = 1990;
var _ykMethod = 'sbp';
var _PKG_PRICES = {1:249, 2:490, 3:1290, 4:2990};
var _PKG_CREDITS = {1:1, 2:3, 3:10, 4:30};
var _SUB_PRICES = {1:990, 2:1990, 3:4900};
function payTab(mode) {
_payMode = mode;
document.getElementById('ptab-once').classList.toggle('on', mode==='once');
document.getElementById('ptab-sub').classList.toggle('on', mode==='sub');
document.getElementById('ppane-once').classList.toggle('on', mode==='once');
document.getElementById('ppane-sub').classList.toggle('on', mode==='sub');
_updatePayBtn();
}
function selectPkg(id, price) {
_selPkg = id; _selPkgPrice = price;
document.querySelectorAll('.pkg-card').forEach(function(c){ c.classList.remove('sel'); });
var el = document.getElementById('pkg-'+id);
if (el) el.classList.add('sel');
_updatePayBtn();
}
function selectSub(id, price) {
_selSub = id; _selSubPrice = price;
document.querySelectorAll('.sub-card').forEach(function(c){ c.classList.remove('sel'); });
var el = document.getElementById('sub-'+id);
if (el) el.classList.add('sel');
_updatePayBtn();
}
function _updatePayBtn() {
var price = _payMode === 'once' ? _selPkgPrice : _selSubPrice;
var suffix = _payMode === 'sub' ? '/мес' : '';
var btn = document.getElementById('pay-price-btn');
if (btn) btn.textContent = 'Оплатить ' + price.toLocaleString('ru') + ' ₽' + suffix;
var ykBtn = document.getElementById('yk-pay-btn');
var ykAmt = document.getElementById('yk-amount');
if (ykBtn) ykBtn.textContent = 'Оплатить ' + price.toLocaleString('ru') + ' ₽' + suffix;
if (ykAmt) ykAmt.textContent = price.toLocaleString('ru') + ' ₽';
var ykTtl = document.getElementById('yk-ttl');
if (ykTtl) ykTtl.textContent = _payMode === 'sub' ? 'Подписка · первый платёж' : 'Пополнение баланса';
}
function _refreshBalance() {
var bal = parseInt(localStorage.getItem('zashita_credits') || '0');
var el = document.getElementById('pay-bal-credits');
var sub = document.getElementById('pay-bal-sub');
var btn = document.getElementById('pay-bal-use-btn');
if (el) el.textContent = bal;
if (bal > 0) {
if (sub) sub.textContent = 'Спишем 1 кредит на эту проверку';
if (btn) btn.style.display = '';
} else {
if (sub) sub.textContent = 'Пополните баланс ниже или оплатите разово';
if (btn) btn.style.display = 'none';
}
}
function useBalance() {
var bal = parseInt(localStorage.getItem('zashita_credits') || '0');
if (bal <= 0) return;
localStorage.setItem('zashita_credits', bal - 1);
// record usage
var hist = JSON.parse(localStorage.getItem('zashita_pay_history') || '[]');
hist.unshift({date: new Date().toISOString(), type: 'use', amount: 0, desc: 'Использован 1 кредит'});
localStorage.setItem('zashita_pay_history', JSON.stringify(hist));
toast('✅ 1 кредит использован — запускаю полный анализ');
setTimeout(function(){ go('cabinet'); tab('cases'); }, 1200);
}
// ЮKassa modal
function ykOpen() {
// Валидация B2B перед открытием
if (!_validateB2B()) return;
// Сохраняем реквизиты в localStorage для будущих заказов
var b2b = _getB2BData();
if (b2b) localStorage.setItem('zashita_b2b', JSON.stringify(b2b));
_updatePayBtn();
document.getElementById('yk-overlay').classList.add('on');
}
function ykClose(e) {
if (!e || e.target === document.getElementById('yk-overlay')) {
document.getElementById('yk-overlay').classList.remove('on');
}
}
function ykMethod(m, el) {
_ykMethod = m;
document.querySelectorAll('.yk-method').forEach(function(x){ x.classList.remove('sel'); });
if (el) el.classList.add('sel');
document.getElementById('yk-card-form').style.display = (m === 'card') ? '' : 'none';
}
function fmtCard(inp) {
var v = inp.value.replace(/\D/g,'').substring(0,16);
inp.value = v.replace(/(\d{4})(?=\d)/g,'$1 ');
}
function ykPay() {
var price = _payMode === 'once' ? _selPkgPrice : _selSubPrice;
var credits = _payMode === 'once' ? _PKG_CREDITS[_selPkg] : 999;
// Mock: зачислить кредиты
if (_payMode === 'once') {
var cur = parseInt(localStorage.getItem('zashita_credits') || '0');
localStorage.setItem('zashita_credits', cur + credits);
} else {
localStorage.setItem('zashita_sub_plan', _selSub);
localStorage.setItem('zashita_sub_since', new Date().toISOString());
}
// Save to payment history
var hist = JSON.parse(localStorage.getItem('zashita_pay_history') || '[]');
hist.unshift({
date: new Date().toISOString(),
type: _payMode,
amount: price,
credits: _payMode === 'once' ? credits : null,
method: _ykMethod,
status: 'paid',
desc: _payMode === 'once' ? 'Пополнение баланса +' + credits + ' кредитов' : 'Подписка · ' + ['','Старт','Бизнес','Безлимит'][_selSub]
});
localStorage.setItem('zashita_pay_history', JSON.stringify(hist));
// Save last order for hero screen
localStorage.setItem('zashita_last_order', JSON.stringify({
ttl: 'Баланс пополнен', plan: _payMode === 'once' ? credits + ' кредитов' : 'Подписка',
price: price.toLocaleString('ru') + ' ₽'
}));
ykClose();
toast('✅ Оплата прошла — кредиты зачислены!');
_refreshBalance();
setTimeout(function(){ go('cabinet'); }, 1500);
}
window.addEventListener('DOMContentLoaded', function(){
// default: select pkg-2
selectPkg(2, 490);
_refreshBalance();
});
/* ── ADMIN + BALANCE ── */
// ── Admin dashboard ──
var _ADM_PAYS = [
{date:'28.05.2026',client:'Р.В.',type:'Пакет Бизнес',amount:1290,status:'paid'},
{date:'28.05.2026',client:'М.С.',type:'Подписка Бизнес',amount:1990,status:'paid'},
{date:'27.05.2026',client:'А.П.',type:'Разовая',amount:249,status:'paid'},
{date:'27.05.2026',client:'О.К.',type:'Пакет Старт',amount:490,status:'paid'},
{date:'26.05.2026',client:'В.Н.',type:'Подписка Старт',amount:990,status:'paid'},
{date:'25.05.2026',client:'Т.Ю.',type:'Пакет Бизнес',amount:1290,status:'ref'},
];
var _ADM_REFUNDS = [
{id:1,client:'Т.Ю.',date:'25.05.2026',amount:1290,credits:7,
reason:'Не понравился формат отчёта',urgent:true},
{id:2,client:'С.В.',date:'22.05.2026',amount:490,credits:2,
reason:'Дублирующая покупка',urgent:false},
];
function _initAdmin() {
// Chart
var vals = [28,32,18,45,22,38,29,52,41,34,47,56,39,44,31,58,42,37,49,53,36,41,55,62,48,44,57,59,43,47];
var max = Math.max.apply(null, vals);
var bars = document.getElementById('adm-chart');
var lbls = document.getElementById('adm-chart-lbl');
if (!bars) return;
bars.innerHTML = vals.map(function(v, i){
var h = Math.round((v/max)*100);
return '<div class="chart-bar'+(i===vals.length-1?' cur':'')+'" style="height:'+h+'%" title="'+v+'00 ₽"></div>';
}).join('');
if (lbls) {
var days = [];
for (var i=vals.length;i>0;i--) {
if (i===vals.length||i===vals.length-7||i===vals.length-14||i===vals.length-21||i===1) {
days.push(i+'д');
} else { days.push(''); }
}
lbls.innerHTML = days.map(function(d){ return '<span>'+d+'</span>'; }).join('');
}
// Payments table
var tbody = document.getElementById('adm-pay-table');
if (tbody) {
tbody.innerHTML = _ADM_PAYS.map(function(p){
var badge = p.status==='paid'?'<span class="pay-badge paid">Оплачено</span>':
p.status==='ref'?'<span class="pay-badge ref">Возврат</span>':
'<span class="pay-badge pend">Ожидание</span>';
return '<tr><td>'+p.date+'</td><td>'+p.client+'</td><td>'+p.type+'</td><td><b>'+p.amount.toLocaleString('ru')+'₽</b></td><td>'+badge+'</td></tr>';
}).join('');
}
// Refunds
var rlist = document.getElementById('adm-refunds-list');
if (rlist) {
if (_ADM_REFUNDS.length === 0) {
rlist.innerHTML = '<div style="text-align:center;padding:24px;color:var(--mut);font-size:13px">✅ Активных заявок нет</div>';
} else {
rlist.innerHTML = _ADM_REFUNDS.map(function(r){
return '<div class="refund-card'+(r.urgent?' urgent':'')+'" id="rcard-'+r.id+'">'
+'<div class="refund-body">'
+'<div class="refund-ttl">'+r.client+' · '+r.date+'</div>'
+'<div class="refund-meta">'+r.reason+'</div>'
+'<div class="refund-meta">Неиспользованных кредитов: '+r.credits+'</div>'
+'<div class="refund-actions">'
+'<button class="refund-btn approve" onclick="admRefund('+r.id+',true)">✅ Вернуть '+Math.round(r.amount*(r.credits/(r.credits+1))).toLocaleString('ru')+'₽</button>'
+'<button class="refund-btn decline" onclick="admRefund('+r.id+',false)">✕ Отклонить</button>'
+'</div></div>'
+'<div class="refund-amt">'+r.amount.toLocaleString('ru')+'₽</div>'
+'</div>';
}).join('');
}
}
}
function admRefund(id, approve) {
var card = document.getElementById('rcard-'+id);
if (card) card.style.opacity = '.4';
toast(approve ? '✅ Возврат одобрен — отправлен в ЮKassa' : '✕ Заявка отклонена — клиент уведомлён');
setTimeout(function(){ if(card) card.remove(); }, 800);
}
// ── Balance tab ──
function _refreshBalanceTab() {
var credits = parseInt(localStorage.getItem('zashita_credits') || '0');
var subPlan = localStorage.getItem('zashita_sub_plan');
var el = document.getElementById('bal-credits-num');
var sub = document.getElementById('bal-sub-status');
if (el) el.textContent = credits;
if (sub) {
var subNames = {1:'Старт', 2:'Бизнес', 3:'Безлимит'};
sub.textContent = subPlan ? ('Подписка ' + (subNames[subPlan]||'') + ' активна 💛') : 'Подписка не активна';
}
// History
var hist = JSON.parse(localStorage.getItem('zashita_pay_history') || '[]');
var hel = document.getElementById('bal-history');
if (hel) {
if (!hist.length) {
hel.innerHTML = '<div style="text-align:center;padding:24px;color:var(--mut);font-size:13px">Платежей пока нет</div>';
} else {
hel.innerHTML = hist.slice(0,10).map(function(h){
var d = new Date(h.date);
var ds = d.getDate()+'.'+(d.getMonth()+1)+'.'+d.getFullYear();
var isCredit = h.type === 'use';
return '<div class="pay-hist-item">'
+'<div class="phi-left"><div class="phi-desc">'+h.desc+'</div><div class="phi-date">'+ds+'</div></div>'
+'<div class="phi-right"><div class="phi-amt '+(isCredit?'db':'cr')+'">'
+(isCredit?'1 кредит':'+'+(h.credits||'∞')+' кредитов')+'</div>'
+(h.amount?'<div class="phi-status">'+h.amount.toLocaleString('ru')+'₽</div>':'')
+'</div></div>';
}).join('');
}
}
}
function toggleRefundForm() {
var f = document.getElementById('ref-form');
if (f) f.style.display = f.style.display === 'none' ? '' : 'none';
}
function submitRefund() {
var reason = (document.getElementById('ref-reason')||{}).value || '';
var credits = parseInt(localStorage.getItem('zashita_credits') || '0');
if (credits <= 0) { toast('⚠ Неиспользованных кредитов нет'); return; }
// Save refund request
var refs = JSON.parse(localStorage.getItem('zashita_refund_requests') || '[]');
refs.push({date: new Date().toISOString(), credits: credits, reason: reason, status: 'pending'});
localStorage.setItem('zashita_refund_requests', JSON.stringify(refs));
toggleRefundForm();
toast('✅ Заявка отправлена — ответим в течение 10 рабочих дней');
}
// Hook into tab switch
var _origTab = typeof tab === 'function' ? tab : null;
window.addEventListener('DOMContentLoaded', function(){
_initAdmin();
_refreshBalanceTab();
});
/* ── CHAT → ELENA TRANSITION ── */
function _chatTransition(userText, intent) {
if (intent === 'cabinet') { go('cabinet'); return; }
if (intent === 'create') {
var step1c = document.getElementById('el-step1');
if (step1c) step1c.style.display = 'none';
go('elena');
setTimeout(function(){ elenaIntent('create'); }, 80);
return;
}
// Скрываем el-step1, переходим на экран Елены
var step1 = document.getElementById('el-step1');
if (step1) step1.style.display = 'none';
go('elena');
// Продолжаем разговор — показываем сообщение клиента и получаем ответ API
setTimeout(function() {
var wrap = document.querySelector('.chatwrap');
if (!wrap || !userText) { elenaIntent(intent); return; }
// Пузырь пользователя
var uDiv = document.createElement('div');
uDiv.className = 'msg msg-user';
uDiv.innerHTML = '<div class="bubble user">' + userText.replace(/</g,'&lt;') + '</div>';
wrap.appendChild(uDiv);
// Typing → API → ответ Елены
var tDiv = document.createElement('div');
tDiv.className = 'msg'; tDiv.id = 'transition-typing';
tDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'<div class="hc-typing-dots"><span></span><span></span><span></span></div></div>';
wrap.appendChild(tDiv);
wrap.scrollTop = wrap.scrollHeight;
_elenaApi(userText, intent, function(apiReply, apiActions) {
var t = document.getElementById('transition-typing'); if (t) t.remove();
var reply = apiReply || 'Расскажите подробнее — помогу разобраться.';
_chatHistory.push({role:'user', content: userText});
_chatHistory.push({role:'assistant', content: reply});
_saveHistory();
var eDiv = document.createElement('div');
eDiv.className = 'msg';
eDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' + reply + '</div>';
wrap.appendChild(eDiv);
if (apiActions && apiActions.length) _renderElenaActions(apiActions, wrap);
wrap.scrollTop = wrap.scrollHeight;
_elenaShowInput();
});
}, 120);
}
/* ── HERO CHAT ── */
// Приглашение в кабинет после первого ответа Елены
function _inviteToCabinet(userText) {
var msgs = document.getElementById('hchat-msgs');
if (!msgs) return;
var div = document.createElement('div');
div.className = 'hc-msg hc-elena';
div.innerHTML =
'<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble" style="display:flex;flex-direction:column;gap:10px">' +
'<div>Давайте зафиксирую Ваш вопрос — так удобнее работать с документами и отслеживать статус.</div>' +
'<button class="btn btn-p" style="padding:8px 18px;font-size:13px;width:fit-content" ' +
'onclick="_goToCabinetWithContext()">📂 Перейти в кабинет →</button>' +
'</div>';
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
}
function _goToCabinetWithContext() {
go('start'); // переходим на главный экран
go('cabinet'); // затем в кабинет
setTimeout(function(){ initReturnChat(); }, 100);
}
var _hchatDone =false;
var _HC_MESSAGES = [
{ text: 'Добрый день 👋', delay: 400, typing: 450 },
{ text: 'Что случилось — расскажите. Помогу разобраться.', delay: 350, typing: 700 }
];
// Карты боли → эмпатичный префикс ответа Елены
var _EMPATHY_MAP = [
{ re: /не платит|долг|задолжал|деньги не|не вернул|взыскать/, prefix: 'Неприятная ситуация — но решаемая. ' },
{ re: /уволят|увольняют|сократят|не выплачивает зарплат|работодатель/, prefix: 'Понимаю, это серьёзно и стрессово. ' },
{ re: /обман|мошенн|обманули|кинули|развели/, prefix: 'Это возмутительно, и с этим можно бороться. ' },
{ re: /срочно|сегодня|завтра|скоро|горит|быстро/, prefix: 'Понял, нужно быстро — разберёмся. ' },
{ re: /боюсь|страшно|не знаю|растерян|непонятно/, prefix: 'Спокойно, разберёмся вместе. ' },
{ re: /расторг|разорвать|выйти из договора/, prefix: 'Понятно — это можно сделать правильно. ' },
{ re: /арендодатель|арендатор|аренда/, prefix: 'Арендные споры — частая история. ' },
{ re: /контрагент|партнёр|поставщик/, prefix: 'Понятно, ситуация с контрагентом. ' },
];
function _getEmpathyPrefix(text) {
var t = text.toLowerCase();
for (var i = 0; i < _EMPATHY_MAP.length; i++) {
if (_EMPATHY_MAP[i].re.test(t)) return _EMPATHY_MAP[i].prefix;
}
return 'Понятно. ';
}
var _HC_REPLIES = {
check: { user: 'Дали договор — хочу проверить', elena: 'Загрузите или вставьте текст — найду все риски и объясню каждый пункт простым языком 🔍', intent: 'check' },
create: { user: 'Нужно составить документ', elena: 'Хорошо — уточним что именно нужно, и составлю под вашу ситуацию ✍️', intent: 'create' },
dispute: { user: 'Контрагент нарушает условия', elena: 'Неприятно, но это решаемо. Загрузите договор — разберём что нарушено и как защититься 📋', intent: 'dispute' },
question: { user: 'Не знаю с чего начать', elena: 'Расскажите ситуацию своими словами — разберёмся вместе, без юридического птичьего языка 💬', intent: 'question' },
power: { user: 'Нужна доверенность', elena: 'Составлю за 15 минут — скажите кому и какие полномочия нужны 📑', intent: 'power' }
};
function _hcAddTyping() {
var msgs = document.getElementById('hchat-msgs');
if (!msgs) return null;
var el = document.createElement('div');
el.className = 'hc-typing';
el.id = 'hc-typing';
el.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg"><div class="hc-typing-dots"><span></span><span></span><span></span></div>';
msgs.appendChild(el);
msgs.scrollTop = msgs.scrollHeight;
return el;
}
function _hcRemoveTyping() {
var el = document.getElementById('hc-typing');
if (el) el.remove();
}
function _hcAddBubble(text, isUser) {
var msgs = document.getElementById('hchat-msgs');
if (!msgs) return;
var row = document.createElement('div');
row.className = 'hc-msg' + (isUser ? ' hc-user-msg' : '');
if (!isUser) {
row.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg"><div class="hc-bubble"></div>';
msgs.appendChild(row);
// typewrite word by word
var bubble = row.querySelector('.hc-bubble');
var words = text.split(' ');
var wi = 0;
function nextWord() {
if (wi < words.length) {
bubble.textContent += (wi > 0 ? ' ' : '') + words[wi++];
msgs.scrollTop = msgs.scrollHeight;
setTimeout(nextWord, 55 + Math.random()*30);
}
}
nextWord();
} else {
row.innerHTML = '<div class="hc-bubble user">' + text + '</div>';
msgs.appendChild(row);
msgs.scrollTop = msgs.scrollHeight;
}
}
function _hcShowControls() {
var replies = document.getElementById('hchat-replies');
var inputRow = document.getElementById('hchat-input-row');
if (replies) { replies.style.display = ''; replies.style.animation = 'hcIn .3s ease forwards'; }
if (inputRow) { inputRow.style.display = ''; }
}
function initHeroChat() {
if (_hchatDone) return;
var msgs = document.getElementById('hchat-msgs');
if (!msgs) return;
_hchatDone = true;
var totalDelay = 0;
_HC_MESSAGES.forEach(function(m, i) {
totalDelay += m.delay;
(function(d){ setTimeout(function(){ _hcAddTyping(); }, d); })(totalDelay);
totalDelay += m.typing;
(function(d, text){ setTimeout(function(){
_hcRemoveTyping();
_hcAddBubble(text, false);
}, d); })(totalDelay, m.text);
});
// Показываем поле ввода СРАЗУ после последнего сообщения — не ждём
var inputDelay = totalDelay + 300;
setTimeout(function(){
var inputRow = document.getElementById('hchat-input-row');
if (inputRow) { inputRow.style.display = ''; inputRow.style.animation = 'hcIn .3s ease forwards'; }
// Фокус в поле
var inp = document.getElementById('hchat-inp');
if (inp) inp.focus();
}, inputDelay);
// Quick replies появляются чуть позже — как подсказка, не главный путь
setTimeout(function(){
var replies = document.getElementById('hchat-replies');
if (replies) { replies.style.display = ''; replies.style.animation = 'hcIn .3s ease forwards'; }
}, inputDelay + 600);
}
function heroChatReply(key) {
var r = _HC_REPLIES[key];
if (!r) return;
// hide controls
var replies = document.getElementById('hchat-replies');
var inputRow = document.getElementById('hchat-input-row');
if (replies) replies.style.display = 'none';
if (inputRow) inputRow.style.display = 'none';
// show user message
_hcAddBubble(r.user, true);
// Elena responds
setTimeout(function(){
_hcAddTyping();
setTimeout(function(){
_hcRemoveTyping();
_hcAddBubble(r.elena, false);
setTimeout(function(){
_chatTransition(r.user, r.intent);
}, 1200);
}, 900);
}, 400);
}
/* ── API ── */
var API_BASE = 'https://wasrusgen1.ru'; // Finnish VPS via Caddy
var _apiAvailable = null; // null=не проверяли, true/false
(function _checkApi(){
fetch(API_BASE + '/api/health', {method:'GET'})
.then(function(r){ return r.json(); })
.then(function(d){ _apiAvailable = d.has_api_key && d.status === 'ok'; })
.catch(function(){ _apiAvailable = false; });
})();
// Текущий контекст договора (deadlines из последнего анализа)
var _contractDeadlines = null;
// ── PERSISTENT MEMORY ──────────────────────────────────────────────────────────
var _MEM_HISTORY = 'zashita_chat_v1'; // история сообщений
var _MEM_CONTRACTS = 'zashita_contracts_v1'; // хранилище договоров
var _MEM_DOSSIER = 'zashita_dossier_v1'; // сжатое досье компании
// История чата — загружается из localStorage, переживает перезагрузку
var _chatHistory = (function() {
try { var h = localStorage.getItem(_MEM_HISTORY); return h ? JSON.parse(h) : []; }
catch(e) { return []; }
})();
function _saveHistory() {
try { localStorage.setItem(_MEM_HISTORY, JSON.stringify(_chatHistory.slice(-60))); }
catch(e) {}
}
// Хранилище договоров
function _saveContract(meta, risks, deadlines, textPreview) {
try {
var contracts = JSON.parse(localStorage.getItem(_MEM_CONTRACTS) || '[]');
var c = {
id: Date.now(),
ts: new Date().toISOString(),
type: (meta && meta.type) || 'Договор',
counterparty: (meta && (meta.counterparty || meta.party_b || meta.contractor)) || '',
start: (meta && meta.start) || '',
end: (meta && meta.end) || '',
deadlines_count: (deadlines || []).length,
risks_critical: (risks || []).filter(function(r){ return r.level === 'critical'; }).length,
top_risks: (risks || []).slice(0,3).map(function(r){ return r.title; }),
preview: (textPreview || '').slice(0, 150)
};
// Дедупликация по типу+контрагенту
contracts = contracts.filter(function(x){
return !(x.type === c.type && x.counterparty === c.counterparty);
});
contracts.unshift(c);
localStorage.setItem(_MEM_CONTRACTS, JSON.stringify(contracts.slice(0, 15)));
} catch(e) {}
}
function _getContracts() {
try { return JSON.parse(localStorage.getItem(_MEM_CONTRACTS) || '[]'); }
catch(e) { return []; }
}
// Досье — структурированные факты о клиенте
function _getDossier() {
try { return JSON.parse(localStorage.getItem(_MEM_DOSSIER) || 'null'); }
catch(e) { return null; }
}
function _updateDossier(patch) {
// patch: {facts:[], decisions:[], open:[]}
try {
var d = _getDossier() || { facts:[], decisions:[], open:[], updated:'' };
if (patch.facts) d.facts = (d.facts.concat(patch.facts)).slice(-20);
if (patch.decisions) d.decisions = (d.decisions.concat(patch.decisions)).slice(-10);
if (patch.open) d.open = patch.open; // заменяем открытые вопросы
d.updated = new Date().toISOString();
localStorage.setItem(_MEM_DOSSIER, JSON.stringify(d));
} catch(e) {}
}
// Извлекаем факты из ответа Елены (без LLM — простые эвристики)
function _extractFacts(userMsg, elenaReply) {
var facts = [], decisions = [], open = [];
var u = (userMsg||'').toLowerCase(), e = (elenaReply||'').toLowerCase();
if (/уже отправил|отправили|подписал|оплатил|сделал/.test(u))
decisions.push(userMsg.slice(0,80));
if (/не успею|не знаю|непонятно|что делать/.test(u))
open.push(userMsg.slice(0,80));
return { facts: facts, decisions: decisions, open: open };
}
// ───────────────────────────────────────────────────────────────────────────────
// Собирает контекст клиента для передачи Елене (включая persistent memory)
function _buildElenaContext() {
var clientName = '';
try {
var b2b = JSON.parse(localStorage.getItem('zashita_b2b') || 'null');
if (b2b && b2b.name) clientName = b2b.name;
if (!clientName) {
var stats = JSON.parse(localStorage.getItem('zashita_intake_stats') || '[]');
if (stats.length && stats[0].name) clientName = stats[0].name;
}
} catch(e) {}
var parts = [];
// 1. Хранилище договоров — Елена знает все договоры клиента
var contracts = _getContracts();
if (contracts.length) {
var cList = contracts.map(function(c) {
var s = c.type;
if (c.counterparty) s += ' с ' + c.counterparty;
if (c.risks_critical) s += ' (' + c.risks_critical + ' крит. риска)';
return s;
}).join('; ');
parts.push('Договоры клиента: ' + cList);
}
// 2. Активные сроки
var activeDL = (_DEADLINES || []).filter(function(d){ return !d.done; });
if (activeDL.length) {
parts.push('Активные сроки: ' + activeDL.slice(0,4).map(function(d){
return '«' + d.title + '» ' + d.date;
}).join(', '));
}
// 3. Досье — факты и решения из прошлых сессий
var dossier = _getDossier();
if (dossier) {
if (dossier.decisions && dossier.decisions.length)
parts.push('Принятые решения: ' + dossier.decisions.slice(-3).join('; '));
if (dossier.open && dossier.open.length)
parts.push('Открытые вопросы: ' + dossier.open.slice(0,2).join('; '));
}
// 4. Текст загруженного договора (текущая сессия)
var contractText = (document.getElementById('el-paste') || {}).value || '';
if (contractText && contractText.length > 50)
parts.push('Текущий договор (загружен): ' + contractText.slice(0, 300));
return {
client_name: clientName,
case_context: parts.join('\n'),
parties: _postalData ? {
counterparty: _postalData.counterparty,
counterEmail: _postalData.counterEmail
} : {}
};
}
// Парсит [ДЕЙСТВИЯ: ...] из ответа Елены и возвращает {text, actions[]}
function _parseElenaActions(reply) {
if (!reply) return { text: reply, actions: [] };
// Ищем [ДЕЙСТВИЯ:...] — возможно обёрнут в ** ** или другой markdown
var match = reply.match(/\*{0,2}\[ДЕЙСТВИЯ:\s*([^\]]+)\]\*{0,2}/);
if (!match) return { text: reply, actions: [] };
var text = reply.replace(match[0], '').replace(/\n{3,}/g, '\n\n').trim();
var actions = match[1].split('·').map(function(a){ return a.trim(); }).filter(Boolean);
return { text: text, actions: actions };
}
// Определяет что делать по клику на кнопку действия
function _elenaActionClick(label) {
var l = label.toLowerCase();
if (/уведомлени|составить|претензи|подготовить/.test(l)) {
// Переходим к созданию документа
var up = document.getElementById('el-step-upload');
if (up) { up.style.display = ''; up.scrollIntoView({behavior:'smooth'}); }
else { go('elena'); setTimeout(function(){ elenaIntent('create'); }, 80); }
} else if (/кабинет|зафиксировать/.test(l)) {
go('cabinet'); tab('sroki');
} else if (/напоминани/.test(l)) {
go('cabinet'); tab('sroki');
} else if (/проверить договор/.test(l)) {
var up2 = document.getElementById('el-step-upload');
if (up2) { up2.style.display = ''; up2.scrollIntoView({behavior:'smooth'}); }
else { go('elena'); setTimeout(function(){ elenaIntent('check'); }, 80); }
} else {
// Уточнить детали — фокус на input
var inp = document.getElementById('el-continue-input') || document.getElementById('intake-custom');
if (inp) { var i = inp.tagName === 'INPUT' ? inp : inp.querySelector('input'); if (i) i.focus(); }
}
}
// Рисует кнопки действий после пузыря Елены
function _renderElenaActions(actions, container) {
if (!actions || !actions.length) return;
var bar = document.createElement('div');
bar.style.cssText = 'display:flex;gap:8px;flex-wrap:wrap;margin:8px 0 0 51px';
actions.forEach(function(label) {
var btn = document.createElement('button');
btn.className = 'svc-btn-detail';
btn.style.cssText = 'font-size:13px;padding:7px 14px';
btn.textContent = label;
btn.onclick = function(){ _elenaActionClick(label); };
bar.appendChild(btn);
});
container.appendChild(bar);
}
function _elenaApi(txt, intent, callback) {
/* Вызывает /api/elena. При ошибке — fallback на шаблон. */
if (!_apiAvailable) { callback(null); return; }
var ctx = _buildElenaContext();
fetch(API_BASE + '/api/elena', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
text: txt,
intent: intent,
history: _chatHistory.slice(-10),
deadlines: _contractDeadlines,
client_name: ctx.client_name,
case_context: ctx.case_context,
parties: ctx.parties
})
})
.then(function(r){ return r.json(); })
.then(function(d){
if (d.reply) {
var parsed = _parseElenaActions(d.reply);
_chatHistory.push({role:'user', content: txt});
_chatHistory.push({role:'assistant', content: parsed.text});
// Сохраняем историю в localStorage после каждого обмена
_saveHistory();
// Обновляем досье фактами из этого обмена
var facts = _extractFacts(txt, parsed.text);
if (facts.decisions.length || facts.open.length)
_updateDossier(facts);
callback(parsed.text, parsed.actions);
} else {
callback(null, []);
}
})
.catch(function(){ callback(null, []); });
}
/* ── КЛАССИФИКАТОР ВХОДЯЩИХ СООБЩЕНИЙ ── */
var _offtopicCount = 0;
function _classifyInput(text) {
var t = text.toLowerCase().trim();
// Приветствия — короткая фраза без делового контекста
if (/^(привет|здравствуй|добрый|здорово|хай|салют|доброе|доброй|хеллоу|hello|hi\b|ку\b|yo\b)/.test(t) && t.length < 50) return 'greeting';
// Явный offtopic — конкретные нерелевантные запросы
// WHITELIST offtopic, НЕ whitelist legal: цена ошибки асимметричная
if (/^(расскажи анекдот|анекдот|какая погода|погода|как дела|что нового|как тебя зовут|поиграй|спой|напой|стих|рецепт|что посмотреть|кино|фильм|музыка)/.test(t) && t.length < 60) return 'offtopic';
// Всё остальное — потенциально юридический запрос
// "контрагент не платит", "меня могут уволить", "проблема с арендой" и т.д.
return 'legal';
}
function heroChatSend() {
var inp = document.getElementById('hchat-inp');
if (!inp) return;
var txt = inp.value.trim();
if (!txt) return;
inp.value = '';
var type = _classifyInput(txt);
var replies = document.getElementById('hchat-replies');
var inputRow = document.getElementById('hchat-input-row');
if (replies) replies.style.display = 'none';
if (inputRow) inputRow.style.display = 'none';
_hcAddBubble(txt, true);
// ── ПРИВЕТСТВИЕ ──
if (type === 'greeting') {
setTimeout(function(){
_hcAddTyping();
setTimeout(function(){
_hcRemoveTyping();
_hcAddBubble('Привет! 👋 Я юридический ИИ-ассистент. Помогаю с договорами, доверенностями, претензиями и другими документами. Что нужно?', false);
if (replies) replies.style.display = '';
if (inputRow) inputRow.style.display = '';
}, 700);
}, 300);
return;
}
// ── ПО ТЕМЕ — нормальный флоу ──
if (type === 'legal') {
_offtopicCount = 0;
var t = txt.toLowerCase();
var intent = 'question';
if (/доверенност/.test(t)) intent = 'power';
else if (/протокол|разногласи|нарушает|не платит|не выплачивает|задолжал|взыскать|спор|должен|долг|не отдаёт|мошенн|обманул|кинул|уволят|увольняют|сокращают/.test(t)) intent = 'dispute';
else if (/состав|написат|создат|подготов|нужен договор|оформить|нужна расписка/.test(t) && !/проверит/.test(t)) intent = 'create';
else if (/проверит|анализ|посмотр|риск|подписать|боюсь подписать|прислали договор|дали договор/.test(t)) intent = 'check';
var reply = _HC_REPLIES[intent] || _HC_REPLIES.question;
// Ответ Елены — реальный API или fallback на шаблон
var empathy = _getEmpathyPrefix(txt);
var fallbackText = empathy + reply.elena;
setTimeout(function(){
_hcAddTyping();
_elenaApi(txt, intent, function(apiReply){
_hcRemoveTyping();
var elenaText = apiReply || fallbackText;
_hcAddBubble(elenaText, false);
// Сохраняем ситуацию для кабинета
try { sessionStorage.setItem('zashita_pending_intake', JSON.stringify({text: txt, intent: intent, ts: Date.now()})); } catch(e){}
// Приглашение в кабинет вместо автоперехода
setTimeout(function(){ _inviteToCabinet(txt); }, 900);
});
}, 400);
return;
}
// ── НЕ ПО ТЕМЕ ──
_offtopicCount++;
if (_offtopicCount === 1) {
// Мягкий редирект
setTimeout(function(){
_hcAddTyping();
setTimeout(function(){
_hcRemoveTyping();
_hcAddBubble('Я специализируюсь на юридических документах — договорах, доверенностях, претензиях 📋 Выберите с чего начнём:', false);
if (replies) replies.style.display = '';
if (inputRow) inputRow.style.display = '';
}, 700);
}, 300);
} else {
// Жёсткий: только кнопки, текст скрыт
setTimeout(function(){
_hcAddTyping();
setTimeout(function(){
_hcRemoveTyping();
_hcAddBubble('Работаю только с юридическими вопросами 🔒 Пожалуйста, выберите из предложенных вариантов:', false);
if (replies) replies.style.display = '';
// inputRow НЕ показываем — только кнопки
}, 700);
}, 300);
}
}
window.addEventListener('DOMContentLoaded', function(){
// Start chat on hero screen if visible
var start = document.getElementById('start');
if (start && start.classList.contains('on')) {
setTimeout(initHeroChat, 300);
}
});
/* ── CASE STATUS HELPERS ── */
// Возвращает ID дел со статусом active или dispute
function _getActiveCaseIds() {
var data = window._CT_DATA || [];
return data
.filter(function(c){ return c.status === 'active' || c.status === 'dispute'; })
.map(function(c){ return c.id; });
}
// Возвращает горящие дедлайны по активным делам
function _getHotDeadlines(days) {
days = days || 7;
var activeCaseIds = _getActiveCaseIds();
var today = new Date(); today.setHours(0,0,0,0);
var hot = [];
(_DEADLINES || []).forEach(function(d){
if (d.done || !d.date) return;
if (d.caseId && activeCaseIds.indexOf(d.caseId) === -1) return;
var dt = new Date(d.date); dt.setHours(0,0,0,0);
var diff = Math.round((dt - today) / 86400000);
if (diff >= -1 && diff <= days) hot.push({ dl: d, diff: diff });
});
hot.sort(function(a,b){ return a.diff - b.diff; });
return hot;
}
/* ── RETURNING CHAT ── */
function _rcAddTyping() {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
var el = document.createElement('div');
el.className = 'hc-typing'; el.id = 'rc-typing';
el.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg"><div class="hc-typing-dots"><span></span><span></span><span></span></div>';
msgs.appendChild(el); msgs.scrollTop = msgs.scrollHeight;
}
function _rcRemoveTyping() {
var el = document.getElementById('rc-typing'); if (el) el.remove();
}
function _rcAddBubble(content, isUser, isRawHtml) {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
var row = document.createElement('div');
row.className = 'hc-msg' + (isUser ? ' hc-user-msg' : '');
var inner = isRawHtml ? content : _rcEscape(content);
if (!isUser) {
row.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg"><div class="hc-bubble">' + inner + '</div>';
} else {
row.innerHTML = '<div class="hc-bubble">' + inner + '</div>';
}
msgs.appendChild(row); msgs.scrollTop = msgs.scrollHeight;
}
function _rcEscape(s) {
return String(s)
.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/\n/g,'<br>');
}
function _rcShowControls() {
var r = document.getElementById('rchat-replies');
var i = document.getElementById('rchat-input-row');
if (r) r.style.display = '';
if (i) i.style.display = '';
}
// Динамические кнопки под контекст пользователя
function _renderRchatButtons(ctx) {
var el = document.getElementById('rchat-replies');
if (!el) return;
// Определяем сценарий
var btns = [];
if (ctx.urgentDL) {
// Горящий срок — главное
var dlTitle = ctx.urgentDL.dl.title;
btns.push({
label: '⚠️ Что сделать по сроку?',
action: 'retChatSend_text("что нужно сделать по сроку ' + dlTitle.slice(0,25) + '")'
});
btns.push({
label: '📝 Составить документ',
action: 'go(\'elena\');setTimeout(function(){elenaIntent(\'create\');},80)'
});
btns.push({
label: '📂 Все мои сроки',
action: 'go(\'cabinet\');tab(\'sroki\')'
});
} else if (ctx.hasContracts && ctx.hasHistory) {
// Есть и договоры, и история — возвращающийся активный клиент
btns.push({
label: '💬 Продолжить разговор',
action: 'document.getElementById(\'rchat-inp\').focus()'
});
btns.push({
label: '📂 Мои дела',
action: 'go(\'cabinet\');tab(\'cases\')'
});
btns.push({
label: '📄 Новый договор',
action: 'go(\'elena\');setTimeout(function(){elenaIntent(\'create\');},80)'
});
} else if (ctx.hasContracts) {
// Есть договоры, нет истории чата
btns.push({
label: '📄 Проверить ещё договор',
action: 'go(\'elena\')'
});
btns.push({
label: '⏱️ Мои сроки',
action: 'go(\'cabinet\');tab(\'sroki\')'
});
btns.push({
label: '✍️ Составить документ',
action: 'go(\'elena\');setTimeout(function(){elenaIntent(\'create\');},80)'
});
} else {
// Новый пользователь — показываем ценность
btns.push({
label: '📄 Проверить договор',
action: 'go(\'elena\')'
});
btns.push({
label: '💬 Задать вопрос',
action: 'go(\'elena\');setTimeout(function(){elenaIntent(\'question\');},80)'
});
btns.push({
label: '✍️ Составить документ',
action: 'go(\'elena\');setTimeout(function(){elenaIntent(\'create\');},80)'
});
}
// Всегда добавляем «Пополнить баланс» если нет кредитов
if (!ctx.credits && !ctx.subPlan && btns.length < 4) {
btns.push({ label: '💳 Пополнить баланс', action: 'go(\'pay\')' });
}
el.innerHTML = btns.map(function(b) {
return '<div class="hc-reply" onclick="' + b.action + '">' + b.label + '</div>';
}).join('');
}
// Хелпер: отправить текст в чат программно
function retChatSend_text(txt) {
var inp = document.getElementById('rchat-inp');
if (inp) { inp.value = txt; retChatSend(); }
}
function initReturnChat() {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
msgs.innerHTML = '';
// Проверяем просроченные напоминания — показываем ПОСЛЕ стандартного приветствия
if (typeof _checkRemindersOnStart === 'function') _checkRemindersOnStart();
// ── Собираем персональные данные ──
var lastOrder = JSON.parse(localStorage.getItem('zashita_last_order') || 'null');
var credits = parseInt(localStorage.getItem('zashita_credits') || '0');
var subPlan = localStorage.getItem('zashita_sub_plan');
var stats = JSON.parse(localStorage.getItem('zashita_intake_stats')|| '[]');
var refunds = JSON.parse(localStorage.getItem('zashita_refund_requests') || '[]');
// Имя из stats или last_order
var name = '';
if (stats.length && stats[0].name) name = stats[0].name;
// Ближайший срочный дедлайн — только по active/dispute делам
var urgentDL = null;
if (typeof _DEADLINES !== 'undefined') {
var activeCaseIds = _getActiveCaseIds();
var today = new Date(); today.setHours(0,0,0,0);
_DEADLINES.forEach(function(d){
if (d.done || !d.date) return;
// Пропускаем дедлайны по завершённым/архивным делам
if (d.caseId && activeCaseIds.indexOf(d.caseId) === -1) return;
var dt = new Date(d.date); dt.setHours(0,0,0,0);
var diff = Math.round((dt - today) / 86400000);
if (diff >= -1 && diff <= 7) {
if (!urgentDL || diff < urgentDL.diff) urgentDL = { dl: d, diff: diff };
}
});
}
// Pending refund
var pendingRefund = refunds.find(function(r){ return r.status === 'pending'; });
// ── Статус-чипсы над чатом ──
var statsRow = document.getElementById('ret-stats-row');
if (statsRow) {
var chips = [];
if (credits > 0) chips.push('<div class="ret-stat green">💳 ' + credits + ' кредитов</div>');
else if (subPlan) chips.push('<div class="ret-stat green">✅ Подписка активна</div>');
if (urgentDL) chips.push('<div class="ret-stat warn">⚠️ ' + urgentDL.dl.title + ' — через ' + urgentDL.diff + ' дн.</div>');
if (pendingRefund) chips.push('<div class="ret-stat warn">↩ Заявка на возврат на рассмотрении</div>');
statsRow.innerHTML = chips.join('');
}
// ── Строим персональные сообщения ──
var greeting = name
? (name + ', рада снова Вас видеть! 👋')
: 'С возвращением! Рада снова Вас видеть 👋';
var context = '';
if (lastOrder && lastOrder.ttl) {
context = 'Помню ваш последний документ: <b>' + lastOrder.ttl + '</b>.';
if (lastOrder.plan) context += ' Тариф: ' + lastOrder.plan + '.';
}
if (credits > 0) {
context += (context ? ' ' : '') + 'На балансе <b>' + credits + ' кредитов</b>.';
} else if (subPlan) {
var sNames = {1:'Старт', 2:'Бизнес', 3:'Безлимит'};
context += (context ? ' ' : '') + 'Подписка <b>' + (sNames[subPlan]||'') + '</b> активна.';
}
// Сохраняем контекст — чтобы ответить на "Что нужно сделать?"
if (urgentDL) {
_rcLastContext = {
type: 'deadline',
dl: urgentDL.dl,
diff: urgentDL.diff,
caseName: urgentDL.dl.caseName || ''
};
}
var callToAction = '';
if (urgentDL) {
var diffLabel = urgentDL.diff < 0
? 'просрочен на ' + Math.abs(urgentDL.diff) + ' дн.'
: urgentDL.diff === 0 ? 'сегодня'
: 'через ' + urgentDL.diff + (urgentDL.diff === 1 ? ' день' : urgentDL.diff < 5 ? ' дня' : ' дней');
callToAction = '⚠️ Срочно: <b>' + urgentDL.dl.title + '</b> — ' + diffLabel
+ '. Напишите «что нужно сделать» — объясню конкретные шаги.';
} else if (pendingRefund) {
callToAction = 'Ваша заявка на возврат в обработке — ответим в течение 10 рабочих дней.';
} else if (!credits && !subPlan) {
callToAction = 'Первые 3 риска — бесплатно. Загрузите договор или задайте вопрос 💬';
} else {
callToAction = 'Чем займёмся сегодня?';
}
// ── Динамические кнопки под контекст ──
_renderRchatButtons({ urgentDL: urgentDL, hasContracts: _getContracts().length > 0,
hasHistory: _chatHistory.length > 0, credits: credits, subPlan: subPlan });
// ── Проверяем незавершённый intake с главного экрана ──
var pendingIntake = null;
try { pendingIntake = JSON.parse(sessionStorage.getItem('zashita_pending_intake') || 'null'); } catch(e){}
if (pendingIntake && pendingIntake.text && (Date.now() - pendingIntake.ts < 600000)) {
// Клиент пришёл из главного чата — показываем подтверждение
sessionStorage.removeItem('zashita_pending_intake');
var intake = pendingIntake;
setTimeout(function(){
_rcAddTyping();
setTimeout(function(){
_rcRemoveTyping();
var quote = intake.text.length > 80 ? intake.text.slice(0, 80) + '...' : intake.text;
var confirmHtml =
'Я правильно Вас поняла:<br>' +
'<div style="background:var(--surf);border-left:3px solid var(--bg);border-radius:0 8px 8px 0;' +
'padding:8px 12px;margin:8px 0;font-style:italic;font-size:13px">' +
'«' + quote.replace(/</g,'&lt;') + '»' +
'</div>' +
'<div style="display:flex;gap:8px;margin-top:10px">' +
'<button class="btn btn-p" style="padding:7px 16px;font-size:13px" ' +
'onclick="_confirmIntake(\'' + intake.intent + '\',\'' + quote.replace(/'/g,"\\'").replace(/"/g,"&quot;") + '\')">✅ Да, всё верно</button>' +
'<button class="svc-btn-detail" onclick="_clarifyIntake()">✏️ Уточнить</button>' +
'</div>';
_rcAddBubble(confirmHtml, false, true);
}, 800);
}, 300);
return; // не показываем стандартное приветствие
}
// ── Стандартная последовательность сообщений ──
var delay = 0;
// msg 1: greeting
delay += 400;
(function(d){ setTimeout(_rcAddTyping, d); })(delay);
delay += 700;
(function(d, txt){ setTimeout(function(){ _rcRemoveTyping(); _rcAddBubble(txt, false); }, d); })(delay, greeting);
// msg 2: context (only if we have something)
if (context) {
delay += 400;
(function(d){ setTimeout(_rcAddTyping, d); })(delay);
delay += 900;
(function(d, txt){ setTimeout(function(){ _rcRemoveTyping(); _rcAddBubble(txt, false); }, d); })(delay, context);
}
// msg 3: call to action
delay += 300;
(function(d){ setTimeout(_rcAddTyping, d); })(delay);
delay += 600;
(function(d, txt){ setTimeout(function(){ _rcRemoveTyping(); _rcAddBubble(txt, false, true); }, d); })(delay, callToAction);
// show controls
delay += 400;
(function(d){ setTimeout(_rcShowControls, d); })(delay);
// Update quick replies based on context
if (urgentDL) {
var repl = document.getElementById('rchat-replies');
if (repl) repl.innerHTML =
'<div class="hc-reply" onclick="go(\'cabinet\');tab(\'sroki\')">⏱️ Срочные сроки</div>' +
'<div class="hc-reply" onclick="go(\'cabinet\');tab(\'cases\')">📂 Мои дела</div>' +
'<div class="hc-reply" onclick="go(\'elena\')">📄 Новый договор</div>' +
'<div class="hc-reply" onclick="go(\'pay\')">💳 Баланс</div>';
}
}
// Контекст последнего сообщения Елены в returning chat
// { type: 'deadline', dl: {title, type, date, quote, diff}, caseName }
var _rcLastContext = null;
// Конкретные шаги по типу срока
var _DL_ACTIONS = {
payment: {
steps: [
'Проверьте реквизиты для оплаты в тексте договора',
'Проведите платёж — сохраните квитанцию или выписку',
'Направьте подтверждение оплаты контрагенту (скан/скриншот)',
'Если уже просрочено — платите немедленно, день просрочки = начисление пени'
],
warn: 'Просрочка оплаты — основание для начисления пени и потенциального расторжения договора.'
},
notification: {
steps: [
'Составьте письменное уведомление (могу помочь — нажмите «Составить»)',
'Отправьте заказным письмом с уведомлением о вручении — это фиксирует дату <button onclick="event.stopPropagation();_showPostalForm()" style="background:var(--tint);color:var(--bg);border:1.5px solid rgba(159,18,57,.25);border-radius:7px;padding:3px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit;vertical-align:middle;margin-left:6px">📄 Бланк ф.119</button>',
'Продублируйте на email контрагента — адрес из договора у нас есть <button onclick="event.stopPropagation();_prepareEmailDraft()" style="background:var(--tint);color:var(--bg);border:1.5px solid rgba(159,18,57,.25);border-radius:7px;padding:3px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit;vertical-align:middle;margin-left:6px">📧 Подготовить письмо</button>',
'Введите трек-номер после отправки:<div style="display:flex;gap:6px;margin-top:7px;flex-wrap:wrap"><input id="postal-track-input" placeholder="14 цифр..." maxlength="14" style="border:1.5px solid var(--line);border-radius:8px;padding:6px 10px;font-size:13px;font-family:inherit;width:160px;outline:none" oninput="_filterDigits(this)" /><button onclick="_saveTrackNumber()" style="background:var(--bg);color:#fff;border:none;border-radius:8px;padding:6px 12px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit">📮 Отследить</button></div>'
],
warn: 'Устное уведомление не имеет юридической силы — только письменное с подтверждением доставки.'
},
renewal: {
steps: [
'Решите: хотите продлить договор или нет?',
'Если НЕ продлеваете — направьте уведомление об отказе от продления до истечения срока',
'Если продлеваете — никаких действий не нужно, договор продлится автоматически',
'Проверьте условия пролонгации: на тех же условиях или изменятся?'
],
warn: 'Пропустить срок уведомления = автоматическое продление на тех же условиях ещё на весь период.'
},
termination: {
steps: [
'Проверьте основание расторжения — оно должно быть в договоре или ГК РФ (ст. 450)',
'Составьте уведомление о расторжении с указанием основания и даты (могу помочь)',
'Направьте заказным письмом с уведомлением о вручении <button onclick="event.stopPropagation();_showPostalForm()" style="background:var(--tint);color:var(--bg);border:1.5px solid rgba(159,18,57,.25);border-radius:7px;padding:3px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit;vertical-align:middle;margin-left:6px">📄 Бланк ф.119</button>',
'Зафиксируйте передачу имущества / возврат предоплаты актом'
],
warn: 'Расторжение без соблюдения порядка = риск иска о возмещении убытков.'
},
penalty: {
steps: [
'Зафиксируйте факт нарушения — скриншоты, переписка, акты',
'Рассчитайте размер пени по формуле из договора',
'Направьте претензию с требованием оплатить пени и основной долг',
'Установите срок ответа — 1030 дней, затем иск'
],
warn: 'Без письменной претензии суд может снизить неустойку по ст. 333 ГК РФ.'
},
other: {
steps: [
'Изучите конкретный пункт договора, к которому относится срок',
'Определите что именно нужно сделать: подписать, оплатить, уведомить или передать',
'Зафиксируйте исполнение письменно — акт, квитанция, письмо',
'Сохраните все подтверждения'
],
warn: ''
}
};
function _getDlActionType(dl) {
if (!dl) return 'other';
var t = (dl.type || '').toLowerCase();
var title = (dl.title || '').toLowerCase();
if (/оплат|платёж|payment/.test(t + title)) return 'payment';
if (/уведомл|notification/.test(t + title)) return 'notification';
if (/пролонг|продлен|renewal/.test(t + title)) return 'renewal';
if (/расторж|termination/.test(t + title)) return 'termination';
if (/пен|штраф|penalty/.test(t + title)) return 'penalty';
return 'other';
}
function _buildDlAnswer(ctx) {
var dl = ctx.dl;
var actionType = _getDlActionType(dl);
var actions = _DL_ACTIONS[actionType] || _DL_ACTIONS.other;
var diffText = '';
if (dl.diff < 0) diffText = '⚠️ Просрочен на ' + Math.abs(dl.diff) + ' дн.';
else if (dl.diff === 0) diffText = '🔴 Срок истекает сегодня';
else diffText = '🕐 Осталось ' + dl.diff + ' дн.';
var stepsHtml = actions.steps.map(function(s, i){
return '<div style="display:flex;gap:10px;margin-bottom:8px;align-items:flex-start">' +
'<span style="background:var(--bg);color:#fff;border-radius:50%;width:20px;height:20px;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:2px">' + (i+1) + '</span>' +
'<div style="font-size:14px;line-height:1.5;flex:1">' + s + '</div></div>';
}).join('');
var warnHtml = actions.warn
? '<div style="background:#fff5f7;border-left:3px solid #9f1239;border-radius:0 8px 8px 0;padding:8px 12px;font-size:12px;color:#6b7280;margin-top:10px">' + actions.warn + '</div>'
: '';
// Кнопки действий — всегда есть "Продолжить с Еленой"
var primaryBtn = '';
if (actionType === 'notification' || actionType === 'termination') {
primaryBtn = '<button class="btn btn-p" style="padding:8px 16px;font-size:13px" ' +
'onclick="_rcTransferToElena(\'create\')">✍️ Составить уведомление</button>';
} else if (actionType === 'payment') {
primaryBtn = '<button class="btn btn-p" style="padding:8px 16px;font-size:13px" ' +
'onclick="_rcTransferToElena(\'dispute\')">💬 Уточнить у Елены</button>';
} else if (actionType === 'renewal') {
primaryBtn = '<button class="btn btn-p" style="padding:8px 16px;font-size:13px" ' +
'onclick="_rcTransferToElena(\'create\')">✍️ Составить отказ от продления</button>';
} else {
primaryBtn = '<button class="btn btn-p" style="padding:8px 16px;font-size:13px" ' +
'onclick="_rcTransferToElena(\'question\')">💬 Продолжить с Еленой</button>';
}
var ctaHtml =
'<div style="display:flex;gap:8px;margin-top:14px;flex-wrap:wrap">' +
primaryBtn +
'<button class="btn btn-o" style="padding:8px 14px;font-size:13px" ' +
'onclick="go(\'cabinet\');tab(\'sroki\')">📋 Все сроки</button>' +
'</div>';
return '<div class="nm">Елена</div>' +
'<b>' + dl.title + '</b> — ' + diffText +
(dl.quote ? '<div style="font-size:12px;color:#6b7280;font-style:italic;margin:6px 0 10px">' + dl.quote + '</div>' : '<br><br>') +
'<b>Что нужно сделать:</b><div style="margin-top:8px">' + stepsHtml + '</div>' +
warnHtml + ctaHtml;
}
// ── ПОЧТА РОССИИ: бланк ф.119 и трекинг ──────────────────────────────────────
function _filterDigits(el) {
el.value = el.value.replace(/[^0-9]/g, '');
}
// Глобальный кэш данных сторон (заполняется при анализе и при открытии бланка)
var _postalData = null;
// Извлекает стороны, адреса и email из текста договора
function _extractParties(text) {
var out = { counterparty: '', counterAddr: '', counterEmail: '', sender: '', senderAddr: '', senderEmail: '' };
if (!text) return out;
// Роли: [контрагент (кому шлём), сторона клиента]
var rolePairs = [
['Арендодатель', 'Арендатор'],
['Исполнитель', 'Заказчик'],
['Продавец', 'Покупатель'],
['Поставщик', 'Покупатель'],
['Подрядчик', 'Заказчик'],
['Принципал', 'Агент'],
['Займодавец', 'Заёмщик'],
['Страховщик', 'Страхователь'],
['Сторона 1', 'Сторона 2'],
];
// Ищем первую встречающуюся пару ролей
var r1 = '', r2 = '';
for (var i = 0; i < rolePairs.length; i++) {
var re1 = new RegExp(rolePairs[i][0], 'i');
var re2 = new RegExp(rolePairs[i][1], 'i');
if (re1.test(text) && re2.test(text)) { r1 = rolePairs[i][0]; r2 = rolePairs[i][1]; break; }
}
// Извлекаем имя по роли: «Арендодатель: ООО "Ромашка"» или «Арендодатель в лице ООО ...»
function extractName(role) {
var re = new RegExp(
role + '[\\s\\S]{0,20}?' +
'((?:ООО|ОАО|ЗАО|ПАО|АО|НКО|ИП|Гр(?:ажданин|ажданка)?|физическое лицо)?' +
'\\s*(?:«[^»]{2,60}»|"[^"]{2,60}"|[А-ЯЁ][а-яёА-ЯЁ\\s-]{2,50}(?:ович|евич|овна|евна|ич|вна)?))',
'im'
);
var m = text.match(re);
return m ? m[1].replace(/^[\s,:—–]+/, '').trim() : '';
}
// Извлекаем адрес из блока роли
function extractAddr(role) {
var roleIdx = text.search(new RegExp(role, 'i'));
if (roleIdx < 0) return '';
var chunk = text.slice(roleIdx, roleIdx + 600);
var m = chunk.match(/(?:юр(?:идический)?\.?\s*адрес|адрес(?:\s*места?\s*нахожден(?:ия|ие))?|место\s*нахожден(?:ия|ие)|зарег(?:истрирован)?\s*по адресу|почтовый адрес)[:\s]+([^\n;]{10,120})/i);
return m ? m[1].trim() : '';
}
// Извлекаем email из блока роли (500 символов вокруг упоминания)
function extractEmail(role) {
var roleIdx = text.search(new RegExp(role, 'i'));
if (roleIdx < 0) return '';
var chunk = text.slice(roleIdx, roleIdx + 500);
var m = chunk.match(/[\w.+-]+@[\w.-]+\.[a-zа-яё]{2,}/i);
return m ? m[0] : '';
}
if (r1) {
out.counterparty = (r1 ? r1 + ': ' : '') + extractName(r1);
out.counterAddr = extractAddr(r1);
out.counterEmail = extractEmail(r1);
out.sender = (r2 ? r2 + ': ' : '') + extractName(r2);
out.senderAddr = extractAddr(r2);
out.senderEmail = extractEmail(r2);
}
return out;
}
function _showPostalForm() {
var dlName = (_rcLastContext && _rcLastContext.dl) ? _rcLastContext.dl.title : 'Уведомление';
var caseName = (_rcLastContext && _rcLastContext.caseName) ? _rcLastContext.caseName : '';
var about = dlName + (caseName ? ' по договору «' + caseName + '»' : '');
// 1. Пытаемся извлечь стороны из текста договора
var contractText = (document.getElementById('el-paste') || {}).value || '';
var parties = _extractParties(contractText);
// 2. Данные отправителя из localStorage (B2B-реквизиты)
var senderName = '', senderAddr = '';
try {
var b2b = JSON.parse(localStorage.getItem('zashita_b2b') || 'null');
if (b2b && b2b.name) { senderName = b2b.name; senderAddr = b2b.addr || ''; }
} catch(e) {}
// B2B из формы оплаты (если открыта сейчас)
if (!senderName) {
var nameEl = document.getElementById('b2b-name');
var addrEl = document.getElementById('b2b-addr');
if (nameEl && nameEl.value) { senderName = nameEl.value; senderAddr = addrEl ? addrEl.value : ''; }
}
// Сохраняем глобально для email-черновика
_postalData = {
counterparty: parties.counterparty || '',
counterAddr: parties.counterAddr || '',
counterEmail: parties.counterEmail || '',
sender: senderName || parties.sender || '',
senderAddr: senderAddr || parties.senderAddr || '',
senderEmail: parties.senderEmail || '',
about: about
};
// Приоритет: договор > localStorage > пусто
var recipientVal = _postalData.counterparty;
var recAddrVal = _postalData.counterAddr;
var senderVal = _postalData.sender;
var sendAddrVal = _postalData.senderAddr;
var el = document.getElementById('postal-form-overlay');
if (!el) return;
document.getElementById('pf-about').value = about;
document.getElementById('pf-recipient').value = recipientVal;
document.getElementById('pf-rec-addr').value = recAddrVal;
document.getElementById('pf-sender').value = senderVal;
document.getElementById('pf-send-addr').value = sendAddrVal;
// Подсветим незаполненные поля
['pf-recipient','pf-rec-addr','pf-sender','pf-send-addr'].forEach(function(id) {
var inp = document.getElementById(id);
if (inp) inp.style.borderColor = inp.value ? 'var(--line)' : 'rgba(159,18,57,.4)';
});
el.classList.add('on');
}
function _postalClose() {
var el = document.getElementById('postal-form-overlay');
if (el) el.classList.remove('on');
}
function _postalPrint() {
var recipient = document.getElementById('pf-recipient').value || '____________________';
var recAddr = document.getElementById('pf-rec-addr').value || '____________________';
var sender = document.getElementById('pf-sender').value || '____________________';
var sendAddr = document.getElementById('pf-send-addr').value || '____________________';
var about = document.getElementById('pf-about').value || '____________________';
var w = window.open('','_blank','width=700,height=600');
w.document.write('<!DOCTYPE html><html><head><meta charset="UTF-8">'+
'<title>Бланк ф.119 — Уведомление о вручении</title>'+
'<style>'+
'body{font-family:Arial,sans-serif;font-size:13px;padding:24px;max-width:600px;margin:0 auto;color:#111}'+
'h2{font-size:16px;text-align:center;margin-bottom:4px}'+
'.sub{text-align:center;font-size:11px;color:#666;margin-bottom:20px}'+
'.row{border-bottom:1px solid #111;min-height:24px;margin-bottom:10px;padding-bottom:3px}'+
'.lbl{font-size:10px;color:#555;margin-bottom:2px}'+
'.val{font-size:13px}'+
'.box{border:1px solid #111;padding:8px;margin-bottom:10px;min-height:40px;font-size:11px;color:#888}'+
'.footer{margin-top:24px;font-size:10px;color:#888;border-top:1px solid #ccc;padding-top:10px}'+
'.logo{text-align:center;font-size:10px;color:#9F1239;font-weight:700;margin-bottom:16px;letter-spacing:1px}'+
'@media print{.no-print{display:none}}'+
'</style></head><body>'+
'<div class="logo">ЗАЩИТА · AI-юридический помощник · wasrusgen1.ru/protect</div>'+
'<h2>УВЕДОМЛЕНИЕ О ВРУЧЕНИИ</h2>'+
'<div class="sub">Форма ф.119 · Почтовые правила РФ</div>'+
'<div class="lbl">Кому (получатель)</div><div class="row val">'+recipient+'</div>'+
'<div class="lbl">Адрес получателя</div><div class="row val">'+recAddr+'</div>'+
'<div class="lbl">О вложении</div><div class="row val">'+about+'</div>'+
'<div style="height:16px"></div>'+
'<div class="lbl">Отправитель</div><div class="row val">'+sender+'</div>'+
'<div class="lbl">Адрес отправителя (для возврата уведомления)</div><div class="row val">'+sendAddr+'</div>'+
'<div style="height:16px"></div>'+
'<div class="box">Для служебных отметок почтового отделения:<br><br><br></div>'+
'<div class="footer">'+
'Дата вручения: _____________ &nbsp;&nbsp; Подпись получателя: _____________<br>'+
'Сгенерировано сервисом ЗАЩИТА · i@wasrusgen.ru · Отчётный документ хранить вместе с делом'+
'</div>'+
'<div style="margin-top:20px;text-align:center" class="no-print">'+
'<button onclick="window.print()" style="background:#9f1239;color:#fff;border:none;border-radius:8px;padding:10px 24px;font-size:14px;font-weight:700;cursor:pointer">🖨️ Распечатать бланк</button>'+
'</div>'+
'</body></html>');
w.document.close();
}
function _prepareEmailDraft() {
var pd = _postalData || {};
var dlTitle = (_rcLastContext && _rcLastContext.dl) ? _rcLastContext.dl.title : 'уведомление';
var caseName = (_rcLastContext && _rcLastContext.caseName) ? _rcLastContext.caseName : '';
var toEmail = pd.counterEmail || '';
var toName = pd.counterparty || 'контрагент';
var fromName = pd.sender || 'клиент';
var about = pd.about || dlTitle;
// Если _postalData не заполнен — попробуем распарсить сейчас
if (!pd.counterparty) {
var contractText = (document.getElementById('el-paste') || {}).value || '';
var parties = _extractParties(contractText);
toEmail = parties.counterEmail || '';
toName = parties.counterparty || 'контрагент';
fromName = parties.sender || fromName;
_postalData = Object.assign({}, parties, { about: about });
}
var subject = 'Уведомление: ' + dlTitle + (caseName ? ' · ' + caseName : '');
var body =
'Уважаемый(ая) ' + toName.replace(/^[^:]+:\s*/, '') + ',\n\n' +
'Настоящим письмом уведомляю Вас о следующем:\n' +
about + '.\n\n' +
'Одновременно направляю настоящее уведомление заказным письмом ' +
'с уведомлением о вручении (Почта России).\n\n' +
'Прошу подтвердить получение данного письма ответным сообщением ' +
'на адрес электронной почты отправителя.\n\n' +
'С уважением,\n' + fromName.replace(/^[^:]+:\s*/, '') + '\n' +
'Дата: ' + new Date().toLocaleDateString('ru-RU');
var ov = document.getElementById('email-draft-overlay');
if (!ov) return;
document.getElementById('ed-to').value = toEmail;
document.getElementById('ed-subject').value = subject;
document.getElementById('ed-body').value = body;
// Если email найден — подсветим зелёным, иначе — рамка предупреждения
var toInp = document.getElementById('ed-to');
toInp.style.borderColor = toEmail ? 'var(--ok)' : 'rgba(159,18,57,.4)';
if (!toEmail) toInp.placeholder = 'Email не найден в договоре — введите вручную';
ov.classList.add('on');
}
function _emailDraftClose() {
var ov = document.getElementById('email-draft-overlay');
if (ov) ov.classList.remove('on');
}
function _emailCopy() {
var to = (document.getElementById('ed-to') || {}).value || '';
var subject = (document.getElementById('ed-subject') || {}).value || '';
var body = (document.getElementById('ed-body') || {}).value || '';
var full = 'Кому: ' + to + '\nТема: ' + subject + '\n\n' + body;
navigator.clipboard.writeText(full).then(function(){
toast('📋 Письмо скопировано — вставьте в почтовый клиент');
_emailDraftClose();
}).catch(function(){
toast('Скопируйте текст вручную');
});
}
function _emailOpenClient() {
var to = encodeURIComponent((document.getElementById('ed-to') || {}).value || '');
var subject = encodeURIComponent((document.getElementById('ed-subject') || {}).value || '');
var body = encodeURIComponent((document.getElementById('ed-body') || {}).value || '');
window.open('mailto:' + to + '?subject=' + subject + '&body=' + body, '_blank');
}
function _saveTrackNumber() {
var input = document.getElementById('postal-track-input');
var num = input ? input.value.replace(/\D/g,'') : '';
if (!num || num.length < 8) { toast('Введите трек-номер (минимум 8 цифр)'); return; }
// Сохраняем в контекст дела
if (_rcLastContext) _rcLastContext.trackNumber = num;
var url = 'https://www.pochta.ru/tracking#' + num;
var html = '<div class="nm">Елена</div>' +
'Трек-номер <b>' + num + '</b> сохранён в дело 📦<br><br>' +
'<a href="' + url + '" target="_blank" ' +
'style="display:inline-flex;align-items:center;gap:7px;background:var(--tint);color:var(--bg);'+
'border:1.5px solid rgba(159,18,57,.25);border-radius:9px;padding:9px 14px;font-size:13px;font-weight:700;text-decoration:none">'+
'📮 Отследить на Почте России →</a>'+
'<div style="font-size:12px;color:var(--mut);margin-top:10px">'+
'Когда придёт бумажное уведомление о вручении — это ваше <b>официальное подтверждение даты</b>. '+
'Сохраните его как документ по делу.</div>';
_rcAddBubble(html, false, true);
_rcShowControls();
if (input) input.value = '';
}
// ─────────────────────────────────────────────────────────────────────────────
function _rcTransferToElena(intent) {
// Переносим контекст дедлайна в полный экран Елены
if (_rcLastContext && _rcLastContext.type === 'deadline') {
var dl = _rcLastContext.dl;
var contextMsg = 'У меня срок: ' + dl.title;
if (dl.caseName) contextMsg += ' по договору «' + dl.caseName + '»';
if (dl.diff !== undefined) {
contextMsg += dl.diff < 0
? ' (просрочен на ' + Math.abs(dl.diff) + ' дн.)'
: dl.diff === 0 ? ' (сегодня)' : ' (через ' + dl.diff + ' дн.)';
}
// Кладём в историю чата — Елена будет знать контекст
_chatHistory.push({ role: 'user', content: contextMsg });
// Переходим на экран Елены
go('elena');
setTimeout(function(){
// Скрываем el-step1, показываем чат
var step1 = document.getElementById('el-step1');
if (step1) step1.style.display = 'none';
// Добавляем контекстное сообщение в chatwrap
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
// Пузырь пользователя
var uDiv = document.createElement('div');
uDiv.className = 'msg msg-user';
uDiv.innerHTML = '<div class="bubble user">' + _rcEscape(contextMsg) + '</div>';
wrap.appendChild(uDiv);
// Елена отвечает с контекстом
var typingDiv = document.createElement('div');
typingDiv.className = 'msg'; typingDiv.id = 'elena-ctx-typing';
typingDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'<div class="hc-typing-dots"><span></span><span></span><span></span></div></div>';
wrap.appendChild(typingDiv);
wrap.scrollTop = wrap.scrollHeight;
// Реальный ответ через API или шаблон
var fallbackReply = _getElenaCtxReply(intent, dl);
_elenaApi(contextMsg, intent, function(apiReply, apiActions){
var t = document.getElementById('elena-ctx-typing');
if (t) t.remove();
var reply = apiReply || fallbackReply;
_chatHistory.push({ role: 'assistant', content: reply });
var eDiv = document.createElement('div');
eDiv.className = 'msg';
eDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' + _rcEscape(reply) + '</div>';
wrap.appendChild(eDiv);
if (apiActions && apiActions.length) _renderElenaActions(apiActions, wrap);
wrap.scrollTop = wrap.scrollHeight;
// Показываем поле ввода Елены.
// Если пришли из дедлайна с intent=create — НЕ показываем generic picker
// (Елена уже сказала что за документ нужен). Просто открываем чат-поле.
setTimeout(function(){
if (intent === 'create' && _rcLastContext && _rcLastContext.dl) {
_elenaShowInput(); // только поле ввода, без picker
} else {
elenaIntent(intent);
}
}, 600);
});
}, 120);
} else {
go('elena');
setTimeout(function(){ elenaIntent(intent); }, 120);
}
}
// Показывает постоянный чат-бар внизу экрана Елены
function _elenaShowInput() {
var bar = document.getElementById('el-chat-bar');
if (bar) {
bar.style.display = '';
setTimeout(function(){
var inp = document.getElementById('el-chat-inp');
if (inp) inp.focus();
}, 200);
}
}
// Отправка из чат-бара
function _elChatSend() {
var inp = document.getElementById('el-chat-inp');
if (!inp) return;
var txt = inp.value.trim(); if (!txt) return;
inp.value = '';
var wrap = document.querySelector('.chatwrap');
if (!wrap) return;
// Пузырь пользователя
var uDiv = document.createElement('div');
uDiv.className = 'msg msg-user';
uDiv.innerHTML = '<div class="bubble user">' + txt.replace(/</g, '&lt;') + '</div>';
wrap.appendChild(uDiv);
wrap.scrollTop = wrap.scrollHeight;
// Елена отвечает
var tDiv = document.createElement('div');
tDiv.className = 'msg'; tDiv.id = 'el-chat-typing';
tDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'<div class="hc-typing-dots"><span></span><span></span><span></span></div></div>';
wrap.appendChild(tDiv);
wrap.scrollTop = wrap.scrollHeight;
_elenaApi(txt, 'question', function(apiReply, apiActions) {
var t = document.getElementById('el-chat-typing'); if (t) t.remove();
var reply = apiReply || 'Уточните, пожалуйста.';
_chatHistory.push({role:'user', content: txt});
_chatHistory.push({role:'assistant', content: reply});
_saveHistory();
var eDiv = document.createElement('div');
eDiv.className = 'msg';
eDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' + _rcEscape(reply) + '</div>';
wrap.appendChild(eDiv);
if (apiActions && apiActions.length) _renderElenaActions(apiActions, wrap);
wrap.scrollTop = wrap.scrollHeight;
// После 2 обменов — предлагаем оценку + монетизацию
if (_chatHistory.length >= 4) {
setTimeout(function(){ _checkAndOfferService(wrap); }, 1200);
}
});
}
// ── МОНЕТИЗАЦИОННЫЙ ФЛОУ ─────────────────────────────────────────────────────
var _serviceOffered = false; // показываем оффер один раз за сессию
function _checkAndOfferService(wrap) {
if (_serviceOffered) return;
if (!wrap) return;
var credits = parseInt(localStorage.getItem('zashita_credits') || '0');
var contractText = (document.getElementById('el-paste') || {}).value || '';
var ctx = _buildElenaContext();
// Запрашиваем оценку ситуации
fetch(API_BASE + '/api/estimate', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
situation: _chatHistory.slice(-4).map(function(m){ return m.role + ': ' + m.content; }).join('\n'),
contract_text: contractText.slice(0, 2000),
case_context: ctx.case_context,
history: _chatHistory.slice(-6)
})
})
.then(function(r){ return r.json(); })
.then(function(data){
if (data.error) return;
_serviceOffered = true;
if (credits > 0) {
_showServiceOffer(wrap, data, credits);
} else {
_showFreePreview(wrap, data);
}
})
.catch(function(){});
}
function _showServiceOffer(wrap, data, credits) {
// Есть баланс — предлагаем конкретную услугу
var recommended = (data.catalog || [])[0];
if (!recommended) return;
var div = document.createElement('div');
div.className = 'msg';
div.innerHTML =
'<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'<div style="margin-bottom:8px">' + (data.summary || '') + '</div>' +
'<div class="svc-order-card" style="margin:8px 0">' +
'<div style="font-weight:700;font-size:14px">' + recommended.name + '</div>' +
'<div style="font-size:12px;color:var(--mut);margin:4px 0">' + recommended.desc + '</div>' +
'<div style="font-size:15px;font-weight:700;color:var(--bg);margin:8px 0">' +
recommended.price.toLocaleString('ru') + ' ₽ · ' + recommended.credits + ' ' +
(recommended.credits === 1 ? 'кредит' : 'кредита') +
'</div>' +
'<div style="font-size:12px;color:#6b7280;margin-bottom:10px">' +
'На вашем балансе: <b>' + credits + ' кредитов</b>' +
'</div>' +
'<div style="display:flex;gap:8px">' +
'<button class="btn btn-p" style="padding:8px 16px;font-size:13px" ' +
'onclick="_confirmService(\'' + recommended.id + '\',' + recommended.credits + ')">✅ Подтвердить</button>' +
'<button class="svc-btn-detail" onclick="_showFreePreview(document.querySelector(\'.chatwrap\'),null)">Посмотреть бесплатно</button>' +
'</div>' +
'</div>' +
'</div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
function _showFreePreview(wrap, data) {
if (!wrap) return;
// Нет баланса — показываем превью + ущерб + варианты
var risks = (data && data.risks) ? data.risks.slice(0, 3) : [];
var damageMin = (data && data.damage_min) ? data.damage_min.toLocaleString('ru') : '?';
var damageMax = (data && data.damage_max) ? data.damage_max.toLocaleString('ru') : '?';
var catalog = (data && data.catalog) ? data.catalog : [];
var risksHtml = risks.map(function(r){
var cls = r.severity === 'critical' ? '#dc2626' : r.severity === 'high' ? '#d97706' : '#2563eb';
return '<div style="border-left:3px solid ' + cls + ';padding:6px 10px;margin:4px 0;background:#fafafa;border-radius:0 6px 6px 0;font-size:13px">' +
'<b>' + r.title + '</b><br><span style="color:#6b7280">' + r.preview + '</span></div>';
}).join('');
var catalogHtml = catalog.map(function(s){
return '<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--line)">' +
'<div><div style="font-weight:600;font-size:13px">' + s.name + '</div>' +
'<div style="font-size:11px;color:var(--mut)">' + s.desc + '</div></div>' +
'<button class="btn btn-p" style="padding:5px 12px;font-size:12px;white-space:nowrap" onclick="go(\'pay\')">' +
s.price.toLocaleString('ru') + ' ₽</button>' +
'</div>';
}).join('');
var div = document.createElement('div');
div.className = 'msg';
div.innerHTML =
'<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
// Превью рисков
(risks.length ? '<div style="font-weight:600;margin-bottom:6px">Ключевые проблемные места:</div>' + risksHtml : '') +
// Оценка ущерба
(data && data.damage_min ?
'<div style="background:#fef2f2;border:1.5px solid #fecaca;border-radius:10px;padding:10px 14px;margin:10px 0">' +
'<div style="font-size:12px;color:#6b7280;margin-bottom:2px">Примерный ущерб если не урегулировать</div>' +
'<div style="font-size:18px;font-weight:700;color:#dc2626">' + damageMin + ' — ' + damageMax + ' ₽</div>' +
'<div style="font-size:11px;color:#6b7280;margin-top:2px">' + ((data && data.damage_comment) || '') + '</div>' +
'</div>' : '') +
// Уговор к действию
'<div style="margin:10px 0;font-size:13px">Пополните счёт — и я окажу услугу в полном объёме. В вашей ситуации возможны следующие варианты:</div>' +
// Каталог услуг
(catalogHtml ? '<div style="margin-top:4px">' + catalogHtml + '</div>' : '') +
'</div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
function _confirmService(serviceId, credits) {
var cur = parseInt(localStorage.getItem('zashita_credits') || '0');
if (cur < credits) {
toast('Недостаточно кредитов — пополните баланс');
go('pay');
return;
}
// Блокируем кредиты (в реальном продукте — резервирование на сервере)
localStorage.setItem('zashita_credits', String(cur - credits));
localStorage.setItem('zashita_reserved_service', serviceId);
toast('✅ ' + credits + ' кредит(а) зарезервированы — выполняю услугу');
var wrap = document.querySelector('.chatwrap');
if (wrap) {
var div = document.createElement('div');
div.className = 'msg';
div.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>' +
'Кредиты зарезервированы. Приступаю к работе — готово будет через несколько секунд.' +
'</div></div>';
wrap.appendChild(div);
div.scrollIntoView({behavior:'smooth'});
}
}
// _elenaContinueSend → заменён на _elChatSend через постоянный чат-бар
function _getElenaCtxReply(intent, dl) {
var name = dl.title || 'срок';
if (intent === 'create') {
// Определяем что за документ нужен из названия дедлайна
var n = (name || '').toLowerCase();
if (/уведомит|уведомлен/.test(n))
return 'Понял — нужно подготовить уведомление по сроку «' + name + '». Кому адресуем и что именно сообщаем?';
if (/претензи/.test(n))
return 'Подготовлю претензию по сроку «' + name + '». Уточните: какая сумма требования и что именно нарушено?';
if (/оплат|оплачен/.test(n))
return 'По сроку «' + name + '» — нужно подготовить документ об оплате. Это счёт, акт или что-то другое?';
return 'Понял — нужно составить документ по сроку «' + name + '». Опишите что именно — подготовлю.';
}
if (intent === 'dispute') return 'Разберёмся со сроком «' + name + '». Расскажите что именно произошло — дам конкретный план.';
return 'Продолжаем по сроку «' + name + '». Что именно хотите уточнить?';
}
function _confirmIntake(intent, quote) {
// Клиент подтвердил ситуацию → фиксируем и начинаем работу
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
// Пузырь клиента
var uDiv = document.createElement('div');
uDiv.className = 'hc-msg hc-user';
uDiv.innerHTML = '<div class="hc-bubble">Да, всё верно</div>';
msgs.appendChild(uDiv);
// Сохраняем в историю
_chatHistory.push({role: 'user', content: quote});
_saveHistory();
// Елена: фиксирует дело → запускает аудит документов
setTimeout(function(){
_rcAddTyping();
var ctx = quote;
_elenaApi('Клиент подтвердил ситуацию: ' + ctx + '. Зафиксируй и определи тип договора (аренда/подряд/купля-продажа/трудовой/займ/ДДУ/недвижимость) — одним словом.',
intent,
function(apiReply) {
_rcRemoveTyping();
var reply = apiReply || 'Хорошо, зафиксировала.';
_chatHistory.push({role: 'assistant', content: reply});
_saveHistory();
_rcAddBubble(reply, false);
_updateDossier({ facts: ['Зафиксировано дело: ' + ctx] });
// Определяем тип договора из ответа и запроса
var contractType = _detectContractType(ctx + ' ' + reply);
// Пауза → показываем аудит документов в чате
setTimeout(function(){
_showDocAuditInChat(contractType, intent);
}, 800);
});
}, 400);
}
function _clarifyIntake() {
// Клиент хочет уточнить
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
var div = document.createElement('div');
div.className = 'hc-msg hc-elena';
div.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble">Уточните, пожалуйста — опишите ситуацию своими словами.</div>';
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
_rcShowControls();
}
function retChatSend() {
var inp = document.getElementById('rchat-inp');
if (!inp) return;
var txt = inp.value.trim(); if (!txt) return;
inp.value = '';
var r = document.getElementById('rchat-replies');
var i = document.getElementById('rchat-input-row');
if (r) r.style.display = 'none'; if (i) i.style.display = 'none';
_rcAddBubble(txt, true);
var t = txt.toLowerCase();
// ── Вопрос о действии по активному контексту срока ──
var isActionQ = /что (нужно|делать|сделать|предлож|посовет|рекоменд)|как (быть|поступить|действовать|решить)|помоги|что значит|объясни|расскажи|подробн|дальше|следующий шаг|с чего начат|что важно|какие шаги|как быть/.test(t);
if (isActionQ && _rcLastContext && _rcLastContext.type === 'deadline') {
setTimeout(function(){
_rcAddTyping();
setTimeout(function(){
_rcRemoveTyping();
var html = _buildDlAnswer(_rcLastContext);
_rcAddBubble(html, false, true);
_rcShowControls();
}, 800);
}, 300);
return;
}
// ── Если API доступен — спрашиваем Елену реально ──
var isLegalQ = !/баланс|кредит|оплат|мои дела|кабинет/.test(t);
// ── API недоступен но есть контекст дедлайна — показываем шаги без редиректа ──
if (!_apiAvailable && _rcLastContext && _rcLastContext.type === 'deadline' && isLegalQ) {
setTimeout(function(){
_rcAddTyping();
setTimeout(function(){
_rcRemoveTyping();
var html = _buildDlAnswer(_rcLastContext);
_rcAddBubble(html, false, true);
_rcShowControls();
}, 800);
}, 300);
return;
}
if (isLegalQ && _apiAvailable) {
setTimeout(function(){
_rcAddTyping();
var ctx = _rcLastContext && _rcLastContext.type === 'deadline'
? '\n[Контекст: клиент спрашивает о сроке «' + _rcLastContext.dl.title + '» по договору ' + (_rcLastContext.caseName || '') + ']'
: '';
var _ectx = _buildElenaContext();
fetch(API_BASE + '/api/elena', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
text: txt + ctx,
intent: 'question',
history: _chatHistory.slice(-10),
deadlines: _contractDeadlines,
client_name: _ectx.client_name,
case_context: _ectx.case_context,
parties: _ectx.parties
})
})
.then(function(res){ return res.json(); })
.then(function(data){
_rcRemoveTyping();
var raw = data.reply || '...';
var parsed = _parseElenaActions(raw);
_chatHistory.push({role:'user', content: txt});
_chatHistory.push({role:'assistant', content: parsed.text});
_saveHistory();
_rcAddBubble(parsed.text, false);
// Рендерим кнопки действий в контейнер returning chat
if (parsed.actions && parsed.actions.length) {
var rcMsgs = document.getElementById('rchat-msgs');
if (rcMsgs) _renderElenaActions(parsed.actions, rcMsgs);
}
_rcShowControls();
})
.catch(function(){
_rcRemoveTyping();
_rcFallbackReply(t);
});
}, 300);
return;
}
// ── Fallback — шаблонные редиректы ──
_rcFallbackReply(t);
}
function _rcFallbackReply(t) {
var intent = 'question';
if (/срок|уведомл|дедлайн/.test(t)) intent = 'deadlines';
else if (/баланс|кредит|оплат/.test(t)) intent = 'pay';
else if (/новый|загруз|проверит|договор/.test(t)) intent = 'new';
else if (/дела|кабинет/.test(t)) intent = 'cabinet';
var replies = {
deadlines: { elena: 'Открываю ваши сроки — всё самое срочное там 📋', action: function(){ go('cabinet'); tab('sroki'); } },
pay: { elena: 'Показываю варианты пополнения 💳', action: function(){ go('pay'); } },
new: { elena: 'Загружайте новый документ — разберём 🔍', action: function(){ go('elena'); } },
cabinet: { elena: 'Открываю ваши дела 📂', action: function(){ go('cabinet'); } },
question: { elena: 'Перехожу к полному чату — там отвечу подробнее 💬', action: function(){ go('elena'); } }
};
var resp = replies[intent] || replies.question;
setTimeout(function(){
_rcAddTyping();
setTimeout(function(){
_rcRemoveTyping();
_rcAddBubble(resp.elena, false);
if (resp.action) setTimeout(resp.action, 900);
_rcShowControls();
}, 700);
}, 300);
}
/* ── СЕРВИСНАЯ КАРТОЧКА (доверенность и др.) ── */
function _showSvcDetail(btn) {
var card = btn.closest('.svc-order-card');
if (!card || card.querySelector('.svc-detail-inp-row')) return;
btn.style.display = 'none';
var row = document.createElement('div');
row.className = 'svc-detail-inp-row';
row.innerHTML =
'<input id="svc-detail-inp" placeholder="Кому? Какие полномочия? Напр: продажа авто, получение документов…" ' +
'style="flex:1;border:1.5px solid #e5e7eb;border-radius:11px;padding:9px 12px;font-size:13px;outline:none;font-family:inherit" ' +
'onkeydown="if(event.key===\'Enter\')_sendSvcDetail()">' +
'<button class="hc-send-btn" onclick="_sendSvcDetail()">→</button>';
card.appendChild(row);
setTimeout(function(){ var i=document.getElementById('svc-detail-inp'); if(i)i.focus(); }, 60);
}
function _sendSvcDetail() {
var inp = document.getElementById('svc-detail-inp');
var txt = inp ? inp.value.trim() : '';
if (!txt) { if(inp) inp.focus(); return; }
var wrap = document.querySelector('.chatwrap');
// Пузырь пользователя
var uDiv = document.createElement('div');
uDiv.className = 'msg msg-user';
uDiv.innerHTML = '<div class="bubble user">' + txt + '</div>';
if (wrap) wrap.appendChild(uDiv);
// Скрываем карточку
var card = document.querySelector('#el-service-power .svc-order-card');
if (card) card.style.display = 'none';
// Ответ Елены
setTimeout(function(){
var eDiv = document.createElement('div');
eDiv.className = 'msg';
eDiv.innerHTML = '<div class="av"><img src="logos/elena-photo.jpg"></div>' +
'<div class="bubble"><div class="nm">Елена</div>Принято! Подготовлю черновик доверенности по вашему описанию — просмотрите и оплатите после согласования.' +
'<br><button class="btn btn-p" style="margin-top:10px;padding:8px 18px;font-size:13px" onclick="go(\'pay\')">Оплатить 990 ₽ →</button></div>';
if (wrap) { wrap.appendChild(eDiv); eDiv.scrollIntoView({behavior:'smooth'}); }
}, 800);
}
/* ── ГОЛОСОВОЙ ВВОД ── */
var _voiceActive = false;
var _voiceRecog = null;
function toggleVoice(targetId, btnId) {
var inputId = targetId || 'intake-custom';
var btn = document.getElementById(btnId || 'voice-btn');
var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) {
var hint = document.getElementById('voice-hint');
if (hint) hint.textContent = '⚠ Голосовой ввод не поддерживается в этом браузере';
return;
}
if (_voiceActive) {
if (_voiceRecog) _voiceRecog.stop();
return;
}
_voiceRecog = new SR();
_voiceRecog.lang = 'ru-RU';
_voiceRecog.interimResults = true;
_voiceRecog.continuous = false;
_voiceRecog.onstart = function() {
_voiceActive = true;
if (btn) { btn.classList.add('recording'); btn.textContent = '🔴'; btn.title = 'Остановить'; }
var hint = document.getElementById('voice-hint');
if (hint) hint.textContent = '🎙 Говорите…';
};
_voiceRecog.onresult = function(e) {
var transcript = '';
for (var i = e.resultIndex; i < e.results.length; i++) {
transcript += e.results[i][0].transcript;
}
var inp = document.getElementById(inputId);
if (inp) inp.value = transcript;
};
_voiceRecog.onend = function() {
_voiceActive = false;
if (btn) { btn.classList.remove('recording'); btn.textContent = '🎙'; btn.title = 'Голосовой ввод'; }
var hint = document.getElementById('voice-hint');
if (hint) hint.textContent = '';
};
_voiceRecog.onerror = function(e) {
_voiceActive = false;
if (btn) { btn.classList.remove('recording'); btn.textContent = '🎙'; btn.title = 'Голосовой ввод'; }
var hint = document.getElementById('voice-hint');
if (hint) hint.textContent = e.error === 'not-allowed' ? '⚠ Разрешите доступ к микрофону' : '⚠ Ошибка записи: ' + e.error;
};
_voiceRecog.start();
}
// Скрыть mic если нет поддержки
window.addEventListener('DOMContentLoaded', function() {
var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) {
var btn = document.getElementById('voice-btn');
if (btn) btn.classList.add('no-support');
}
});
/* ── CRM CABINET ── */
function ctSearchFilter(q) {
q = q.toLowerCase();
var rows = document.querySelectorAll('#ct-tbody tr');
rows.forEach(function(r){ r.style.display = q && !r.textContent.toLowerCase().includes(q) ? 'none' : ''; });
}
function updateKPI() {
if (!window.CT_DATA) return;
var total = CT_DATA.length;
var work = CT_DATA.filter(function(r){ return r.status==='work'; }).length;
var urg = CT_DATA.filter(function(r){ return r.risk==='high' && r.open; }).length;
var done = CT_DATA.filter(function(r){ return r.status==='done'; }).length;
var set = function(id,v){ var el=document.getElementById(id); if(el) el.textContent=v; };
set('kpi-total', total); set('kpi-work', work); set('kpi-urg', urg); set('kpi-done', done);
var b = document.getElementById('side-badge-cases');
if(b) b.textContent = CT_DATA.filter(function(r){ return r.open; }).length;
}
var TAB_TITLES = { cases:'Мои дела', case:'Текущее дело', sroki:'Сроки', shab:'Шаблоны' };
var _origTabCRM = window.tab;
window.tab = function(id) {
if (_origTabCRM) _origTabCRM(id);
var t = document.getElementById('main-hdr-title');
if (t) t.textContent = TAB_TITLES[id] || 'Кабинет';
// Показать/скрыть пункт "Текущее дело" в сайдбаре
var caseLink = document.getElementById('t-case');
if (caseLink) caseLink.style.display = id === 'case' ? 'flex' : 'none';
if (id === 'cases') setTimeout(updateKPI, 60);
};
window.addEventListener('DOMContentLoaded', function(){
setTimeout(updateKPI, 100);
});
/* ── СОЗДАНИЕ ДОКУМЕНТА ── */
var _selectedDocType = null;
var _DOC_FORMS = {
agent: {
title: 'Агентский договор',
fields: [
{id:'df-principal',label:'Принципал (вы)',placeholder:'ИП Васильев Руслан Геннадьевич',hint:'Полное наименование или ФИО',col:'half'},
{id:'df-agent',label:'Агент (контрагент)',placeholder:'ООО «Зов Ресторанс»',hint:'Полное наименование',col:'half'},
{id:'df-subject',label:'Предмет поручения',placeholder:'Поиск и привлечение клиентов для ресторана',hint:'Что именно делает агент',col:'full'},
{id:'df-fee',label:'Вознаграждение',placeholder:'150 000 руб./мес.',hint:'Сумма и периодичность',col:'half'},
{id:'df-term',label:'Срок договора',placeholder:'1 год с даты подписания',hint:'Дата начала и окончания',col:'half'},
{id:'df-court',label:'Подсудность',placeholder:'По месту нахождения Принципала',hint:'Суд при возникновении споров',col:'half'},
{id:'df-nds',label:'НДС',placeholder:'Не облагается — УСН',hint:'Или: в т.ч. НДС 20%',col:'half'},
]
},
trust: {
title: 'Доверенность',
fields: [
{id:'df-principal',label:'Доверитель',placeholder:'ИП Васильев Руслан Геннадьевич',hint:'ФИО и паспортные данные',col:'half'},
{id:'df-attorney',label:'Поверенный',placeholder:'Иванов Иван Иванович',hint:'ФИО и паспортные данные',col:'half'},
{id:'df-powers',label:'Полномочия',placeholder:'Подписание договоров, переговоры с контрагентами',hint:'Что может делать поверенный',col:'full'},
{id:'df-term',label:'Срок действия',placeholder:'1 год',hint:'Дата выдачи и срок',col:'half'},
{id:'df-subst',label:'Передоверие',placeholder:'Без права передоверия',hint:'Разрешено ли передоверие',col:'half'},
]
},
claim: {
title: 'Претензия',
fields: [
{id:'df-sender',label:'Отправитель',placeholder:'ИП Васильев Р.Г.',hint:'Ваши данные',col:'half'},
{id:'df-receiver',label:'Получатель',placeholder:'ООО «Контрагент»',hint:'Кому направляете',col:'half'},
{id:'df-basis',label:'Основание',placeholder:'Договор поставки № 12 от 01.04.2025',hint:'Договор или обязательство',col:'full'},
{id:'df-violation',label:'Нарушение',placeholder:'Не оплачена поставка товара на сумму 500 000 руб.',hint:'Что нарушено',col:'full'},
{id:'df-demand',label:'Требование',placeholder:'Оплатить в течение 10 дней',hint:'Что требуете и в какой срок',col:'full'},
{id:'df-penalty',label:'Неустойка',placeholder:'0,1% за каждый день просрочки',hint:'Размер пени по договору или ст. 395 ГК',col:'half'},
]
},
supply: {
title: 'Договор поставки',
fields: [
{id:'df-supplier',label:'Поставщик',placeholder:'ООО «Поставщик»',hint:'Полное наименование поставщика',col:'half'},
{id:'df-buyer',label:'Покупатель (вы)',placeholder:'ИП Васильев Р.Г.',hint:'Ваши данные',col:'half'},
{id:'df-goods',label:'Товар',placeholder:'Кухонное оборудование, ассортимент по спецификации',hint:'Наименование и описание товара',col:'full'},
{id:'df-price',label:'Сумма поставки',placeholder:'850 000 руб. в т.ч. НДС 20%',hint:'Общая сумма и НДС',col:'half'},
{id:'df-shipdate',label:'Срок поставки',placeholder:'30 дней с даты оплаты',hint:'Когда должен быть доставлен товар',col:'half'},
{id:'df-quality',label:'Гарантия качества',placeholder:'12 месяцев с даты поставки',hint:'Срок гарантии и условия возврата',col:'half'},
{id:'df-penalty',label:'Неустойка за просрочку',placeholder:'0,1% в день от стоимости непоставленного',hint:'Размер пени',col:'half'},
]
},
rent: {
title: 'Договор аренды',
fields: [
{id:'df-landlord',label:'Арендодатель',placeholder:'ООО «Бизнес-Центр Плюс»',hint:'Кто сдаёт',col:'half'},
{id:'df-tenant',label:'Арендатор (вы)',placeholder:'ИП Васильев Р.Г.',hint:'Ваши данные',col:'half'},
{id:'df-object',label:'Объект аренды',placeholder:'Офис 305, ул. Красная, 1, г. Краснодар, 45 кв.м',hint:'Адрес, площадь, кадастровый номер',col:'full'},
{id:'df-rent',label:'Арендная плата',placeholder:'80 000 руб./мес. без НДС',hint:'Размер и периодичность оплаты',col:'half'},
{id:'df-term',label:'Срок аренды',placeholder:'1 год с 01.06.2025',hint:'Дата начала и конца',col:'half'},
{id:'df-deposit',label:'Обеспечительный платёж',placeholder:'160 000 руб. (2 месяца)',hint:'Размер и условия возврата',col:'half'},
{id:'df-repair',label:'Ремонт и улучшения',placeholder:'Только с письменного согласия арендодателя',hint:'Кто несёт расходы',col:'half'},
]
},
nda: {
title: 'NDA / Конфиденциальность',
fields: [
{id:'df-party1',label:'Сторона 1 (вы)',placeholder:'ИП Васильев Р.Г.',hint:'Ваши данные',col:'half'},
{id:'df-party2',label:'Сторона 2',placeholder:'ООО «Партнёр»',hint:'Контрагент',col:'half'},
{id:'df-info',label:'Что является конфиденциальным',placeholder:'Бизнес-планы, клиентская база, технологии производства',hint:'Конкретный перечень закрытой информации',col:'full'},
{id:'df-term',label:'Срок действия NDA',placeholder:'3 года с даты подписания',hint:'Период действия обязательств',col:'half'},
{id:'df-exceptions',label:'Исключения',placeholder:'Общедоступная информация, данные от третьих лиц',hint:'Что НЕ является конфиденциальным',col:'half'},
{id:'df-penalty',label:'Ответственность за разглашение',placeholder:'500 000 руб. за каждый случай',hint:'Штраф или способ расчёта убытков',col:'half'},
]
},
labor: {
title: 'Трудовой договор',
fields: [
{id:'df-employer',label:'Работодатель',placeholder:'ИП Васильев Р.Г.',hint:'Наименование или ФИО ИП',col:'half'},
{id:'df-employee',label:'Работник',placeholder:'Иванова Мария Петровна',hint:'ФИО, паспортные данные',col:'half'},
{id:'df-position',label:'Должность',placeholder:'Менеджер по работе с клиентами',hint:'Точное наименование должности',col:'full'},
{id:'df-salary',label:'Оклад',placeholder:'60 000 руб./мес. до вычета НДФЛ',hint:'Размер оклада и доп. выплаты',col:'half'},
{id:'df-schedule',label:'График работы',placeholder:'Пн–Пт, 09:0018:00',hint:'Режим рабочего времени',col:'half'},
{id:'df-start',label:'Дата начала',placeholder:'01.06.2025',hint:'Когда приступает к работе',col:'half'},
{id:'df-probation',label:'Испытательный срок',placeholder:'3 месяца',hint:'Или «без испытания»',col:'half'},
]
},
dismiss: {
title: 'Соглашение о расторжении',
fields: [
{id:'df-party1',label:'Сторона 1 (вы)',placeholder:'ИП Васильев Р.Г.',hint:'Ваши данные',col:'half'},
{id:'df-party2',label:'Сторона 2',placeholder:'ООО «Зов Ресторанс»',hint:'Контрагент',col:'half'},
{id:'df-contract',label:'Расторгаемый договор',placeholder:'Агентский договор № 5 от 01.03.2025',hint:'Название, номер, дата',col:'full'},
{id:'df-date',label:'Дата расторжения',placeholder:'01.06.2025',hint:'Когда договор прекращает действие',col:'half'},
{id:'df-settlement',label:'Взаиморасчёты',placeholder:'Стороны не имеют взаимных претензий',hint:'Долги, возвраты, штрафы',col:'half'},
{id:'df-return',label:'Возврат имущества',placeholder:'Переданные материалы возвращены по акту',hint:'Что нужно вернуть',col:'full'},
]
}
};
function selectDocType(type, el) {
_selectedDocType = type;
document.querySelectorAll('.doc-type-card').forEach(function(c){ c.classList.remove('sel'); });
if(el) el.classList.add('sel');
var btn = document.getElementById('doc-type-next');
if(btn){ btn.classList.add('show'); btn.textContent = 'Заполнить параметры →'; }
}
function goCreateStep(step) {
document.querySelectorAll('.create-pane').forEach(function(p){ p.classList.remove('on'); });
document.querySelectorAll('.cstep').forEach(function(s,i){
s.classList.toggle('act', i+1 === step);
s.classList.toggle('done', i+1 < step);
var num = s.querySelector('.cstep-num');
if(num) num.textContent = (i+1 < step) ? '✓' : (i+1);
});
if(step === 1){ document.getElementById('cp-type').classList.add('on'); }
else if(step === 2){ buildCreateForm(); document.getElementById('cp-form').classList.add('on'); }
else if(step === 3){ document.getElementById('cp-preview').classList.add('on'); startDocGeneration(); }
window.scrollTo(0,0);
}
function buildCreateForm() {
var type = _selectedDocType || 'agent';
var def = _DOC_FORMS[type] || _DOC_FORMS.agent;
var title = document.getElementById('cf-doc-title');
if(title) title.textContent = def.title;
var grid = document.getElementById('create-form-fields');
if(!grid) return;
grid.innerHTML = def.fields.map(function(f){
var cls = f.col === 'full' ? 'cf-group wide' : 'cf-group';
return '<div class="'+cls+'"><label class="cf-label">'+f.label+'</label><input class="cf-input" id="'+f.id+'" placeholder="'+f.placeholder+'"><span class="cf-hint">'+f.hint+'</span></div>';
}).join('');
}
function startDocGeneration() {
var spinner = document.getElementById('doc-generating');
var preview = document.getElementById('doc-preview-wrap');
if(spinner){ spinner.classList.add('show'); }
if(preview){ preview.classList.remove('show'); }
setTimeout(function(){
if(spinner) spinner.classList.remove('show');
if(preview) preview.classList.add('show');
}, 2200);
}
/* ── ЧАСЫ ── */
(function(){
var DAYS = ['Воскресенье','Понедельник','Вторник','Среда','Четверг','Пятница','Суббота'];
var MONTHS = ['января','февраля','марта','апреля','мая','июня','июля','августа','сентября','октября','ноября','декабря'];
function tickClock(){
var now = new Date();
var dayEl = document.getElementById('sc-day');
var dateEl = document.getElementById('sc-date');
var timeEl = document.getElementById('sc-time');
if(dayEl) dayEl.textContent = DAYS[now.getDay()];
if(dateEl) dateEl.textContent = now.getDate() + ' ' + MONTHS[now.getMonth()] + ' ' + now.getFullYear();
if(timeEl){
var h = String(now.getHours()).padStart(2,'0');
var m = String(now.getMinutes()).padStart(2,'0');
var s = String(now.getSeconds()).padStart(2,'0');
timeEl.textContent = h + ':' + m + ':' + s;
}
}
setInterval(tickClock, 1000);
window.addEventListener('DOMContentLoaded', tickClock);
})();
/* ── СЧЁТЧИК ПРОТОКОЛА ── */
var _riskApplied = new Set();
function applyRisk(num, btn) {
_riskApplied.add(num);
btn.textContent = '✅ Применено';
btn.disabled = true;
btn.style.background = '#16a34a';
btn.style.cursor = 'default';
_updateProtocolBar();
toast('✅ Пункт #' + num + ' добавлен в протокол разногласий');
}
function _updateProtocolBar() {
var n = _riskApplied.size;
var bar = document.getElementById('protocol-bar');
var sub = document.getElementById('pb-sub');
var btn = document.getElementById('pb-generate');
if (!bar) return;
if (n > 0) {
bar.classList.add('show');
if (sub) sub.textContent = 'Выбрано пунктов: ' + n + ' из 12 — готов к формированию';
if (btn) btn.textContent = 'Сформировать протокол (' + n + ') →';
}
}
/* ── RISK ACCORDION ── */
function toggleRisk(el) {
var wasOpen = el.classList.contains('expanded');
document.querySelectorAll('.risk-item.expanded').forEach(function(r){ r.classList.remove('expanded'); });
if (!wasOpen) el.classList.add('expanded');
}
/* ── CASE TABS ── */
function caseTab(id, el) {
document.querySelectorAll('.case-pane').forEach(function(p){ p.classList.remove('on'); });
document.querySelectorAll('.case-tab').forEach(function(t){ t.classList.remove('on'); });
var pane = document.getElementById('cp-' + id);
if (pane) pane.classList.add('on');
if (el) el.classList.add('on');
}
/* ── СТАТУС ЗАКАЗА ── */
const OS_DEADLINES = {
protocol: { 1:'до 12 часов', 2:'до 24 часов', 3:'до 48 часов', sub:'после получения файла договора' },
redact: { 1:'до 12 часов', 2:'до 24 часов', 3:'до 48 часов', sub:'после получения файла договора' },
clean: { 1:'до 24 часов', 2:'до 48 часов', 3:'до 72 часов', sub:'после получения файла договора' },
partner: { 1:'до 24 часов', 2:'до 48 часов', 3:'до 72 часов', sub:'после получения файла договора' },
consult: { 1:'2 часа', 2:'сегодня', 3:'24 часа', sub:'отвечаем после получения оплаты' },
reply: { 1:'до 12 часов', 2:'до 12 часов', 3:'до результата', sub:'после получения переписки с контрагентом' }
};
const OS_STEP2 = {
protocol:'Юрист изучает договор',
redact:'Юрист готовит редакцию',
clean:'Юрист чистит текст',
partner:'Юрист пишет партнёрскую версию',
consult:'Юрист готовится к консультации',
reply:'Юрист составляет ответ'
};
const OS_ELENA = {
protocol:'Заказ получен — уже передала юристу. <b>Пришлите договор</b> в Telegram <a href="https://t.me/VASRUSGEN" target="_blank">@wasrusgen1</a> — без него начать не получится. Как только файл придёт — сразу приступаем 💛',
redact:'Заказ получен. <b>Пришлите договор</b> в Telegram <a href="https://t.me/VASRUSGEN" target="_blank">@wasrusgen1</a> — и обозначьте что хотите изменить, если есть конкретные пункты. Начнём сразу 💛',
clean:'Заказ получен. <b>Пришлите договор</b> в Telegram <a href="https://t.me/VASRUSGEN" target="_blank">@wasrusgen1</a> — юрист сделает его читаемым и структурированным. Жду файл 💛',
partner:'Заказ получен. <b>Пришлите договор</b> в Telegram <a href="https://t.me/VASRUSGEN" target="_blank">@wasrusgen1</a> — и кратко опишите вашу позицию в переговорах. Это поможет сделать текст работающим 💛',
consult:'Заказ получен. <b>Пришлите договор</b> в Telegram <a href="https://t.me/VASRUSGEN" target="_blank">@wasrusgen1</a> — и список вопросов, или просто напишите «разбери сам». AI найдёт все риски и ответит по вашей ситуации. Отвечаем в течение 2 часов 💛',
reply:'Заказ получен. <b>Пришлите переписку с контрагентом и текст возражения</b> в Telegram <a href="https://t.me/VASRUSGEN" target="_blank">@wasrusgen1</a> — юрист составит ответ с вашей правовой позицией 💛'
};
function showOrderStatus() {
const d = DELIVS[_selDeliv] || {};
const pKey = 'p' + _selPlan;
const pArr = d[pKey] || [];
const dl = (OS_DEADLINES[_selDeliv] || {})[_selPlan] || 'уточним';
const dlSub = (OS_DEADLINES[_selDeliv] || {}).sub || '';
const set = (id, val) => { const el = document.getElementById(id); if (el) el.innerHTML = val; };
set('os-svc', d.ttl || 'Юридическая услуга');
set('os-plan-name', pArr[1] || ('Тариф ' + _selPlan));
set('os-price', pArr[0] || '');
set('os-orderid', 'Заказ #' + Math.floor(1000 + Math.random() * 8999));
set('os-step2-lbl', (OS_STEP2[_selDeliv] || 'Юрист работает').replace(' ', '<br>'));
set('os-deadline-ttl', 'Срок: ' + dl);
set('os-deadline-sub', dlSub);
set('os-elena-msg', OS_ELENA[_selDeliv] || OS_ELENA.protocol);
set('os-ok-sub', 'Заказ №' + Math.floor(1000 + Math.random() * 8999) + ' передан в работу');
}
/* ── АНАЛИТИКА CUSTOM-ЗАПРОСОВ ── */
const CA_STOPWORDS = new Set(['и','в','на','с','по','для','что','это','как','не','а','но','из','к','о','от','за','до','при','без','под','над','со','об','же','бы','ли','ещё','уже','когда','если','мне','мы','я','вы','он','она','они','то','так','ну','ладно','просто','только','очень','нет','да','быть','есть','это','свой','мой','ваш','наш','его','её','их','все','всё','один','одна','других','другой','которые','который','можно','нужно','надо','хочу','хочется','нужна','нужен','нужно','получить','сделать','чтобы','также']);
function caWordFreq(arr) {
const freq = {};
arr.forEach(r => {
const words = (r.text || '').toLowerCase()
.replace(/[^а-яёa-z\s]/g, ' ')
.split(/\s+/)
.filter(w => w.length > 3 && !CA_STOPWORDS.has(w));
words.forEach(w => { freq[w] = (freq[w] || 0) + 1; });
});
return Object.entries(freq)
.sort((a, b) => b[1] - a[1])
.slice(0, 15);
}
function caFmtDate(ts) {
try {
const d = new Date(ts);
return d.toLocaleDateString('ru-RU', {day:'2-digit',month:'short',hour:'2-digit',minute:'2-digit'});
} catch(e) { return ts; }
}
function caAddToSystem(idx) {
const arr = JSON.parse(localStorage.getItem('zashita_custom_delivs') || '[]');
if (arr[idx]) { arr[idx].added = true; localStorage.setItem('zashita_custom_delivs', JSON.stringify(arr)); }
renderCustomAdmin();
}
function caCopyText(text) {
navigator.clipboard.writeText(text).catch(() => {});
showToast('Скопировано');
}
function caExportCSV() {
const arr = JSON.parse(localStorage.getItem('zashita_custom_delivs') || '[]');
if (!arr.length) return;
const rows = [['Дата','Тип договора','Запрос','Добавлено в систему']];
arr.forEach(r => rows.push([
r.ts || '', (r.ctype || '').replace(/,/g,''),
'"' + (r.text || '').replace(/"/g,'""') + '"',
r.added ? 'да' : 'нет'
]));
const csv = rows.map(r => r.join(',')).join('\n');
const a = document.createElement('a');
a.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent('' + csv);
a.download = 'zashita_custom_' + new Date().toISOString().slice(0,10) + '.csv';
a.click();
}
function caExportJSON() {
const arr = JSON.parse(localStorage.getItem('zashita_custom_delivs') || '[]');
navigator.clipboard.writeText(JSON.stringify(arr, null, 2))
.then(() => showToast('JSON скопирован в буфер'))
.catch(() => showToast('Ошибка копирования'));
}
function caClearAll() {
if (!confirm('Удалить все custom-запросы?')) return;
localStorage.removeItem('zashita_custom_delivs');
renderCustomStats();
renderCustomAdmin();
}
function renderCustomAdmin() {
const arr = JSON.parse(localStorage.getItem('zashita_custom_delivs') || '[]');
const body = document.getElementById('ca-body');
if (!body) return;
if (!arr.length) {
body.innerHTML = '<div class="ca-empty"><div class="ce-icon">📭</div>Пока нет запросов.<br>Они появятся когда клиент нажмёт «Отправить запрос».</div>';
return;
}
const ctypes = [...new Set(arr.map(r => (r.ctype||'').trim()).filter(Boolean))];
const words = caWordFreq(arr);
const added = arr.filter(r => r.added).length;
// Статистика
let html = `<div class="ca-stats">
<div class="ca-stat"><div class="sv">${arr.length}</div><div class="sl">Всего запросов</div></div>
<div class="ca-stat"><div class="sv">${ctypes.length || '—'}</div><div class="sl">Типов договора</div></div>
<div class="ca-stat"><div class="sv">${added}</div><div class="sl">Добавлено в систему</div></div>
</div>`;
// Частотный анализ
if (words.length) {
html += '<div class="ca-section">Частые темы запросов</div><div class="ca-words">';
const maxCnt = words[0][1];
words.forEach(([w, c]) => {
const isTop = c === maxCnt;
html += `<div class="ca-word${isTop?' w-top':''}">${w}<span class="wc">${c}</span></div>`;
});
html += '</div>';
}
// Список запросов
html += '<div class="ca-section">Все запросы</div>';
arr.slice().reverse().forEach((r, i) => {
const realIdx = arr.length - 1 - i;
const ctypeLabel = (r.ctype||'').trim() || 'тип не определён';
html += `<div class="ca-card${r.added?' ca-done':''}">
<div class="cc-head">
<span class="cc-ctype">${ctypeLabel}</span>
<span class="cc-ts">${caFmtDate(r.ts)}</span>
</div>
<div class="cc-text">${r.text || ''}</div>
<div class="cc-actions">
<button class="cc-add" onclick="caAddToSystem(${realIdx})" ${r.added?'disabled':''}>
${r.added ? '✓ Добавлено в систему' : '+ Добавить в систему'}
</button>
<button class="cc-copy" onclick="caCopyText(${JSON.stringify(r.text||'')})">Копировать</button>
</div>
</div>`;
});
// Экспорт
html += `<div class="ca-export">
<button onclick="caExportCSV()">⬇ Скачать CSV</button>
<button onclick="caExportJSON()">📋 Копировать JSON</button>
<button onclick="caClearAll()" style="color:#b91c1c;border-color:#fecaca">🗑 Очистить всё</button>
</div>`;
body.innerHTML = html;
}
function showToast(msg) {
let t = document.getElementById('ca-toast');
if (!t) {
t = document.createElement('div'); t.id = 'ca-toast';
t.style.cssText = 'position:fixed;bottom:90px;left:50%;transform:translateX(-50%);background:#0C0608;color:#fff;padding:9px 18px;border-radius:10px;font-size:13px;z-index:99999;pointer-events:none;transition:opacity .3s';
document.body.appendChild(t);
}
t.textContent = msg; t.style.opacity = '1';
clearTimeout(t._to); t._to = setTimeout(() => { t.style.opacity = '0'; }, 2000);
}
window.addEventListener('DOMContentLoaded', renderStats);
window.addEventListener('DOMContentLoaded', renderCustomStats);
window.addEventListener('DOMContentLoaded', renderCustomAdmin);
function handleHash() {
const h = window.location.hash.slice(1);
if (!h) return;
const [screen, deliv, plan] = h.split(':');
if (deliv) _selDeliv = deliv;
if (plan) _selPlan = parseInt(plan) || 2;
if (screen === 'order-status') { showOrderStatus(); go('order-status'); }
else if (screen) go(screen);
}
window.addEventListener('DOMContentLoaded', handleHash);
window.addEventListener('hashchange', handleHash);
function tab(name){
document.querySelectorAll('.tabpane').forEach(p=>p.classList.toggle('on',p.id==='p-'+name));
if(name==='sroki' && typeof renderDeadlines==='function') renderDeadlines();
if(name==='shab' && typeof renderContextTemplates==='function') renderContextTemplates();
if(name==='requisites' && typeof _loadRequisites==='function') _loadRequisites();
if(name==='casemap' && typeof renderCaseMap==='function') renderCaseMap();
if(name==='team' && typeof renderTeamDashboard==='function') renderTeamDashboard();
if(name==='docs') {
var contracts = typeof _getContracts === 'function' ? _getContracts() : [];
if (contracts.length && typeof renderDocChecklist === 'function') renderDocChecklist(contracts[0].type);
else if (typeof renderDocChecklist === 'function') renderDocChecklist('');
}
if(name==='balance' && typeof _refreshBalanceTab==='function') _refreshBalanceTab();
document.querySelectorAll('.side a').forEach(a=>a.classList.remove('on'));
const map={cases:'t-cases',case:'t-case',sroki:'t-sroki',shab:'t-shab',create:'t-create'};
const el=document.getElementById(map[name]); if(el) el.classList.add('on');
window.scrollTo(0,0);
}
</script>
</div>
<!-- ── ЮKASSA ВИДЖЕТ ── -->
<div class="yk-overlay" id="yk-overlay" onclick="ykClose(event)">
<div class="yk-sheet" id="yk-sheet">
<button class="yk-close" onclick="ykClose()"></button>
<div id="yk-form">
<div class="yk-header">
<div class="yk-logo">ЮMoney<span>·</span>Kassa</div>
<div class="yk-amount" id="yk-amount">2 480 ₽</div>
</div>
<button class="yk-sbp" onclick="ykPaySBP()">
<div class="yk-sbp-icon">СБП</div>
Оплатить через СБП
</button>
<div class="yk-sep">или картой</div>
<div class="yk-field">
<label>Номер карты</label>
<input id="yk-card" type="text" inputmode="numeric" placeholder="0000 0000 0000 0000" maxlength="19" oninput="ykFmtCard(this)" autocomplete="cc-number">
</div>
<div class="yk-row">
<div class="yk-field">
<label>Срок действия</label>
<input id="yk-exp" type="text" inputmode="numeric" placeholder="ММ / ГГ" maxlength="7" oninput="ykFmtExp(this)" autocomplete="cc-exp">
</div>
<div class="yk-field">
<label>CVV / CVC</label>
<input id="yk-cvv" type="password" inputmode="numeric" placeholder="•••" maxlength="4" autocomplete="cc-csc">
</div>
</div>
<button class="yk-pay-btn" id="yk-pay-btn" onclick="ykSubmit()">Оплатить <span id="yk-btn-amount">2 480 ₽</span></button>
<div class="yk-footer">
Платёж защищён · <a href="https://yookassa.ru" target="_blank">ЮKassa</a> ·
<a href="oferta.html" target="_blank">Оферта</a>
</div>
</div>
<div class="yk-success" id="yk-success">
<div class="yk-check"></div>
<div class="yk-sttl">Оплата прошла!</div>
<div class="yk-ssub">Переходим к заказу…</div>
</div>
</div>
</div>
<!-- ЮKassa payment modal -->
<div class="yk-overlay" id="yk-overlay" onclick="ykClose(event)">
<div class="yk-modal" onclick="event.stopPropagation()">
<div class="yk-hdr">
<div class="yk-ttl" id="yk-ttl">Оплата</div>
<button class="yk-close" onclick="ykClose()"></button>
</div>
<div class="yk-amount" id="yk-amount">490 ₽</div>
<div class="yk-methods">
<div class="yk-method yk-sbp sel" onclick="ykMethod('sbp',this)">
<div class="yk-method-ico">📲</div>
<div class="yk-method-lbl">СБП</div>
</div>
<div class="yk-method" onclick="ykMethod('card',this)">
<div class="yk-method-ico">💳</div>
<div class="yk-method-lbl">Карта</div>
</div>
<div class="yk-method" onclick="ykMethod('mir',this)">
<div class="yk-method-ico">🇷🇺</div>
<div class="yk-method-lbl">Мир Pay</div>
</div>
<div class="yk-method" onclick="ykMethod('qr',this)">
<div class="yk-method-ico">📱</div>
<div class="yk-method-lbl">QR-код</div>
</div>
</div>
<div class="yk-card-form" id="yk-card-form" style="display:none">
<input placeholder="Номер карты" maxlength="19" oninput="fmtCard(this)">
<div class="yk-card-row">
<input placeholder="ММ / ГГ" maxlength="5">
<input placeholder="CVV" maxlength="3" type="password">
</div>
</div>
<button class="yk-pay-btn" id="yk-pay-btn" onclick="ykPay()">Оплатить 490 ₽</button>
<div class="yk-secure">🔒 Защищено ЮKassa · 3D Secure · чек на email</div>
</div>
</div>
<!-- ═ ADMIN SCREEN ═ -->
<div class="screen" id="admin">
<div class="topbar">
<img class="topbar-wm" src="logos/logo-zashita-word.svg" alt="ЗАЩИТА">
<span class="ttl">Кабинет администратора</span>
<span class="back back-link" onclick="go('start')">← выйти</span>
</div>
<div class="admin-wrap">
<div class="crumb">Администратор</div>
<h1 style="margin-bottom:20px">Дашборд</h1>
<!-- KPI -->
<div class="admin-kpi">
<div class="akpi">
<div class="akpi-num" style="color:var(--bg)" id="adm-rev">47 300 ₽</div>
<div class="akpi-lbl">Выручка за месяц</div>
<div class="akpi-trend up" id="adm-rev-tr">↑ +18% к прошлому</div>
</div>
<div class="akpi">
<div class="akpi-num" id="adm-clients">134</div>
<div class="akpi-lbl">Клиентов всего</div>
<div class="akpi-trend up">↑ +12 новых</div>
</div>
<div class="akpi">
<div class="akpi-num" id="adm-subs">28</div>
<div class="akpi-lbl">Активных подписок</div>
<div class="akpi-trend up">↑ +3 этой неделе</div>
</div>
<div class="akpi">
<div class="akpi-num" style="color:#ef4444" id="adm-refunds">2</div>
<div class="akpi-lbl">Заявки на возврат</div>
<div class="akpi-trend dn">⚠ Требуют решения</div>
</div>
</div>
<!-- Revenue chart -->
<div class="admin-chart">
<div class="admin-section-hdr">
<div class="admin-section-ttl">Выручка — последние 30 дней</div>
<div class="admin-section-link" onclick="adminChartMode()">По неделям</div>
</div>
<div class="chart-bars" id="adm-chart"></div>
<div class="chart-lbl" id="adm-chart-lbl"></div>
</div>
<!-- Recent payments -->
<div class="admin-section">
<div class="admin-section-hdr">
<div class="admin-section-ttl">Последние платежи</div>
<div class="admin-section-link">Все платежи →</div>
</div>
<table class="pay-table">
<thead><tr><th>Дата</th><th>Клиент</th><th>Тип</th><th>Сумма</th><th>Статус</th></tr></thead>
<tbody id="adm-pay-table"></tbody>
</table>
</div>
<!-- Refund requests -->
<div class="admin-section">
<div class="admin-section-hdr">
<div class="admin-section-ttl">Заявки на возврат</div>
</div>
<div id="adm-refunds-list"></div>
</div>
</div>
</div>
<!-- ── МОДАЛКА: ЧЕРНОВИК EMAIL КОНТРАГЕНТУ ── -->
<style>
#email-draft-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9001;align-items:flex-end;justify-content:center}
#email-draft-overlay.on{display:flex}
</style>
<div id="email-draft-overlay" onclick="if(event.target===this)_emailDraftClose()">
<div onclick="event.stopPropagation()"
style="background:#fff;border-radius:20px 20px 0 0;width:100%;max-width:500px;padding:22px 20px 32px;position:relative;box-shadow:0 -8px 40px rgba(0,0,0,.18);animation:slideUp .22s ease;max-height:92vh;overflow-y:auto">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">
<div>
<div style="font-size:15px;font-weight:800;color:var(--ink)">📧 Черновик письма</div>
<div style="font-size:12px;color:var(--mut)">Подтверждение получения уведомления</div>
</div>
<button onclick="_emailDraftClose()" style="background:var(--surf);border:none;border-radius:50%;width:30px;height:30px;font-size:14px;cursor:pointer;color:var(--mut)"></button>
</div>
<div style="background:var(--tint);border-radius:10px;padding:9px 13px;font-size:12px;color:var(--dark);margin-bottom:14px;line-height:1.5">
Email контрагента подтянут из договора. Отправьте это письмо параллельно с заказным — он дублирует вручение и создаёт цифровой след.
</div>
<div style="display:flex;flex-direction:column;gap:10px">
<div>
<label style="font-size:11px;font-weight:700;color:var(--slate);display:block;margin-bottom:4px">КОМУ</label>
<input id="ed-to" placeholder="email@counterparty.ru"
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink)">
</div>
<div>
<label style="font-size:11px;font-weight:700;color:var(--slate);display:block;margin-bottom:4px">ТЕМА</label>
<input id="ed-subject"
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink)">
</div>
<div>
<label style="font-size:11px;font-weight:700;color:var(--slate);display:block;margin-bottom:4px">ТЕКСТ ПИСЬМА</label>
<textarea id="ed-body" rows="9"
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink);resize:vertical;line-height:1.55"></textarea>
</div>
</div>
<div style="display:flex;gap:8px;margin-top:16px;flex-wrap:wrap">
<button onclick="_emailOpenClient()"
style="flex:1;min-width:150px;background:var(--bg);color:#fff;border:none;border-radius:11px;padding:11px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit">
✉️ Открыть в почте
</button>
<button onclick="_emailCopy()"
style="flex:1;min-width:120px;background:var(--surf);color:var(--ink);border:1.5px solid var(--line);border-radius:11px;padding:11px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">
📋 Копировать
</button>
<button onclick="_emailDraftClose()"
style="background:var(--surf);color:var(--mut);border:1.5px solid var(--line);border-radius:11px;padding:11px 14px;font-size:13px;cursor:pointer;font-family:inherit">
Отмена
</button>
</div>
</div>
</div>
<!-- ── МОДАЛКА: БЛАНК Ф.119 (Почта России) ── -->
<style>
#postal-form-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9000;align-items:flex-end;justify-content:center}
#postal-form-overlay.on{display:flex}
</style>
<div id="postal-form-overlay" onclick="if(event.target===this)_postalClose()">
<div onclick="event.stopPropagation()"
style="background:#fff;border-radius:20px 20px 0 0;width:100%;max-width:500px;padding:22px 20px 32px;position:relative;box-shadow:0 -8px 40px rgba(0,0,0,.18);animation:slideUp .22s ease;max-height:90vh;overflow-y:auto">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
<div>
<div style="font-size:15px;font-weight:800;color:var(--ink)">📄 Бланк ф.119</div>
<div style="font-size:12px;color:var(--mut)">Уведомление о вручении · Почта России</div>
</div>
<button onclick="_postalClose()" style="background:var(--surf);border:none;border-radius:50%;width:30px;height:30px;font-size:14px;cursor:pointer;color:var(--mut)"></button>
</div>
<div style="background:var(--tint);border-radius:10px;padding:10px 13px;font-size:12px;color:var(--dark);margin-bottom:16px;line-height:1.5">
Заполните поля — бланк откроется готовым к печати. Прикрепите к письму на почте, сотрудник заполнит трек-номер.
</div>
<div style="display:flex;flex-direction:column;gap:11px">
<div>
<label style="font-size:11px;font-weight:700;color:var(--slate);display:block;margin-bottom:4px">ПОЛУЧАТЕЛЬ (контрагент)</label>
<input id="pf-recipient" placeholder="ООО Ромашка / Иванов Иван Иванович"
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink)">
</div>
<div>
<label style="font-size:11px;font-weight:700;color:var(--slate);display:block;margin-bottom:4px">АДРЕС ПОЛУЧАТЕЛЯ</label>
<input id="pf-rec-addr" placeholder="119034, г. Москва, ул. Пречистенка, д. 1"
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink)">
</div>
<div>
<label style="font-size:11px;font-weight:700;color:var(--slate);display:block;margin-bottom:4px">О ВЛОЖЕНИИ</label>
<input id="pf-about" placeholder="Уведомление о расторжении договора аренды..."
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink)">
</div>
<div style="border-top:1px solid var(--line);padding-top:12px">
<div style="font-size:11px;font-weight:700;color:var(--slate);margin-bottom:8px">ВАШИ ДАННЫЕ (для возврата уведомления)</div>
<div style="display:flex;flex-direction:column;gap:8px">
<input id="pf-sender" placeholder="Ваше имя / название организации"
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink)">
<input id="pf-send-addr" placeholder="Ваш почтовый адрес"
style="width:100%;border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;outline:none;color:var(--ink)">
</div>
</div>
</div>
<div style="display:flex;gap:10px;margin-top:18px">
<button onclick="_postalPrint()"
style="flex:1;background:var(--bg);color:#fff;border:none;border-radius:11px;padding:12px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit">
🖨️ Открыть и распечатать
</button>
<button onclick="_postalClose()"
style="background:var(--surf);color:var(--mut);border:1.5px solid var(--line);border-radius:11px;padding:12px 16px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">
Отмена
</button>
</div>
</div>
</div>
<script>
// ── COMPRESS SESSION → DOSSIER ─────────────────────────────────────────────────
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') _compressAsync(false);
});
function _compressAsync(showToast) {
if (!_apiAvailable || _chatHistory.length < 4) return;
fetch(API_BASE + '/api/compress', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
history: _chatHistory.slice(-30),
existing_dossier: _getDossier() || {}
})
})
.then(function(r){ return r.json(); })
.then(function(d){
if (d.facts || d.decisions || d.open) {
_updateDossier(d);
if (showToast) toast('💾 Елена запомнила контекст');
}
})
.catch(function(){});
}
// ── МУЛЬТИПОЛЬЗОВАТЕЛЬ / ОРГАНИЗАЦИЯ ────────────────────────────────────────
var _ORG_KEY = 'zashita_org'; // {org_id, name}
var _USER_KEY = 'zashita_user'; // {user_id, name, role, token, org_id}
function _getOrgUser() {
try { return JSON.parse(localStorage.getItem(_USER_KEY) || 'null'); } catch(e){ return null; }
}
function _setOrgUser(user) {
try { localStorage.setItem(_USER_KEY, JSON.stringify(user)); } catch(e){}
}
function _orgHeaders() {
var u = _getOrgUser();
return u ? {'Content-Type':'application/json','X-User-Token': u.token} : {'Content-Type':'application/json'};
}
// Регистрация организации
function _registerOrg() {
var orgName = (document.getElementById('reg-org-name') ||{}).value||'';
var inn = (document.getElementById('reg-inn') ||{}).value||'';
var adminName = (document.getElementById('reg-admin-name') ||{}).value||'';
var adminEmail = (document.getElementById('reg-admin-email')||{}).value||'';
var adminPhone = (document.getElementById('reg-admin-phone')||{}).value||'';
if (!orgName || !adminName) { toast('Заполните название организации и имя'); return; }
fetch(API_BASE + '/api/org/register', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({org_name:orgName, inn:inn, admin_name:adminName,
admin_email:adminEmail, admin_phone:adminPhone})
})
.then(function(r){ return r.json(); })
.then(function(d) {
if (d.error) { toast('Ошибка: ' + d.error); return; }
_setOrgUser({user_id:d.user_id, org_id:d.org_id, role:d.role, token:d.token,
name:adminName, org_name:orgName});
_afterOrgLogin(d.role);
toast('✅ Организация зарегистрирована');
}).catch(function(e){ toast('Ошибка: ' + e.message); });
}
// Вход по токену
function _loginByToken() {
var token = (document.getElementById('org-token-inp')||{}).value||'';
if (!token.trim()) { toast('Введите токен'); return; }
fetch(API_BASE + '/api/org/me', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({token: token.trim()})
})
.then(function(r){ return r.json(); })
.then(function(d) {
if (d.error) { toast('Токен не найден'); return; }
var u = d.user; var o = d.org;
_setOrgUser({user_id:u.id, org_id:u.org_id, role:u.role, token:token.trim(),
name:u.name, org_name:o.name||''});
_afterOrgLogin(u.role);
toast('✅ Добро пожаловать, ' + u.name);
}).catch(function(){ toast('Ошибка входа'); });
}
function _afterOrgLogin(role) {
// Показываем вкладку Команда для manager/admin
var teamTab = document.getElementById('t-team');
if (teamTab) teamTab.style.display = (role === 'manager' || role === 'admin') ? '' : 'none';
// Обновляем нижнюю панель кабинета
_updateSidebarUser();
go('cabinet');
}
function _showOrgLogin() {
var f = document.getElementById('org-login-form');
if (f) f.style.display = f.style.display === 'none' ? '' : 'none';
}
function _updateSidebarUser() {
var u = _getOrgUser();
if (!u) return;
// Обновляем имя в сайдбаре
var nameEl = document.querySelector('.side-user-name');
if (nameEl) nameEl.textContent = u.name;
var orgEl = document.querySelector('.side-user-plan');
if (orgEl) orgEl.textContent = u.org_name || 'Организация';
}
// Дашборд команды
function renderTeamDashboard() {
var el = document.getElementById('team-dashboard');
if (!el) return;
var u = _getOrgUser();
if (!u || (u.role !== 'manager' && u.role !== 'admin')) {
el.innerHTML = '<div style="padding:24px;text-align:center;color:var(--mut)">Только для менеджеров</div>';
return;
}
el.innerHTML = '<div style="padding:20px;text-align:center;color:var(--mut)">Загружаю данные...</div>';
fetch(API_BASE + '/api/org/dashboard', {
method: 'POST', headers: _orgHeaders(), body: JSON.stringify({token: u.token})
})
.then(function(r){ return r.json(); })
.then(function(d) {
if (d.error) { el.innerHTML = '<div style="padding:20px;color:#dc2626">' + d.error + '</div>'; return; }
var html = '<div class="kpi-row" style="margin-bottom:20px">' +
'<div class="kpi-card"><div class="kc-ico">👥</div><div><div class="kc-num">' + d.users.length + '</div><div class="kc-lbl">Сотрудников</div></div></div>' +
'<div class="kpi-card"><div class="kc-ico">📁</div><div><div class="kc-num">' + d.active_cases + '</div><div class="kc-lbl">Активных дел</div></div></div>' +
'<div class="kpi-card kpi-urg"><div class="kc-ico">⚠️</div><div><div class="kc-num">' + d.urgent_cases + '</div><div class="kc-lbl">Срочных</div></div></div>' +
'</div>';
d.users.forEach(function(usr) {
var roleLabel = {user:'Сотрудник', manager:'Менеджер', admin:'Администратор'}[usr.role]||usr.role;
var lastSeen = usr.last_seen ? new Date(usr.last_seen).toLocaleDateString('ru-RU') : 'не заходил';
html += '<div style="border:1.5px solid var(--line);border-radius:12px;padding:14px;margin-bottom:10px">' +
'<div style="display:flex;align-items:center;gap:10px;margin-bottom:' + (usr.cases.length ? '12px' : '0') + '">' +
'<div style="width:36px;height:36px;border-radius:50%;background:var(--bg);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px">' +
(usr.name||'?')[0].toUpperCase() +
'</div>' +
'<div style="flex:1">' +
'<div style="font-weight:700;font-size:14px">' + (usr.name||'—') + '</div>' +
'<div style="font-size:12px;color:var(--mut)">' + roleLabel +
(usr.department ? ' · ' + usr.department : '') + ' · последний вход: ' + lastSeen +
'</div>' +
'</div>' +
'<div style="display:flex;gap:6px">' +
'<span class="chip ' + (usr.cases_urgent ? 'd' : 'ok') + '" style="font-size:11px">' +
usr.cases_active + ' дел' + (usr.cases_urgent ? ' ⚠️'+usr.cases_urgent : '') +
'</span>' +
'</div>' +
'</div>';
if (usr.cases.length) {
html += '<div style="display:flex;flex-direction:column;gap:4px">';
usr.cases.slice(0,3).forEach(function(c){
var riskColor = {high:'#dc2626',medium:'#d97706',low:'#16a34a'}[c.risk_level]||'#6b7280';
html += '<div style="display:flex;align-items:center;gap:8px;padding:6px 8px;background:#f9fafb;border-radius:8px;font-size:12px">' +
'<span style="color:' + riskColor + '">●</span>' +
'<span style="flex:1">' + (c.title||'Без названия') + '</span>' +
'<span style="color:var(--mut)">' + (c.type||'') + '</span>' +
'</div>';
});
html += '</div>';
}
html += '</div>';
});
html += '<button class="btn btn-o" style="padding:8px 16px;font-size:13px;width:100%;margin-top:4px" onclick="_showInviteModal()">+ Пригласить сотрудника</button>';
el.innerHTML = html;
})
.catch(function(){ el.innerHTML = '<div style="padding:20px;color:#dc2626">Ошибка загрузки</div>'; });
}
// Приглашение сотрудника
function _showInviteModal() {
var old = document.getElementById('invite-modal'); if(old) old.remove();
var u = _getOrgUser(); if (!u) return;
var modal = document.createElement('div');
modal.id = 'invite-modal';
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1000;display:flex;align-items:flex-end;justify-content:center';
modal.innerHTML =
'<div style="background:#fff;border-radius:18px 18px 0 0;width:100%;max-width:540px;padding:24px">' +
'<div style="font-weight:700;font-size:15px;margin-bottom:16px">Пригласить сотрудника</div>' +
'<div style="display:flex;flex-direction:column;gap:10px">' +
'<input class="elena-main-inp" id="inv-name" placeholder="Имя и фамилия">' +
'<input class="elena-main-inp" id="inv-email" placeholder="Email (необязательно)" type="email">' +
'<input class="elena-main-inp" id="inv-phone" placeholder="Телефон (необязательно)">' +
'<input class="elena-main-inp" id="inv-dept" placeholder="Отдел (необязательно)">' +
'<select id="inv-role" style="border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;font-family:inherit;color:var(--ink)">' +
'<option value="user">Сотрудник</option>' +
'<option value="manager">Менеджер (видит все дела)</option>' +
'</select>' +
'</div>' +
'<div style="display:flex;gap:8px;margin-top:16px">' +
'<button class="btn btn-p" style="flex:1;padding:10px" onclick="_doInvite()">Создать приглашение</button>' +
'<button class="svc-btn-detail" onclick="document.getElementById(\'invite-modal\').remove()">Отмена</button>' +
'</div>' +
'<div id="invite-result" style="margin-top:12px"></div>' +
'</div>';
document.body.appendChild(modal);
setTimeout(function(){ var i=document.getElementById('inv-name'); if(i) i.focus(); }, 200);
}
function _doInvite() {
var u = _getOrgUser(); if (!u) return;
var name = (document.getElementById('inv-name')||{}).value||'';
if (!name) { toast('Введите имя'); return; }
fetch(API_BASE + '/api/org/invite', {
method: 'POST', headers: _orgHeaders(),
body: JSON.stringify({
token: u.token,
name: name,
email: (document.getElementById('inv-email')||{}).value||'',
phone: (document.getElementById('inv-phone')||{}).value||'',
department: (document.getElementById('inv-dept') ||{}).value||'',
role: (document.getElementById('inv-role') ||{}).value||'user',
})
})
.then(function(r){ return r.json(); })
.then(function(d) {
if (d.error) { toast('Ошибка: '+d.error); return; }
var res = document.getElementById('invite-result');
if (res) res.innerHTML =
'<div style="background:#f0fdf4;border:1.5px solid #86efac;border-radius:10px;padding:12px;font-size:13px">' +
'<div style="font-weight:700;color:#16a34a;margin-bottom:6px">✅ Приглашение создано</div>' +
'<div style="color:#374151;margin-bottom:8px">Токен для входа:</div>' +
'<div style="background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:8px;font-family:monospace;font-size:12px;word-break:break-all">' +
d.invite_token +
'</div>' +
'<button class="svc-btn-detail" style="margin-top:8px;font-size:12px" onclick="navigator.clipboard.writeText(\'' +
d.invite_token + '\').then(function(){toast(\'Скопировано!\')})">📋 Скопировать</button>' +
'</div>';
renderTeamDashboard();
}).catch(function(){ toast('Ошибка'); });
}
// Инициализация мультипользователя при загрузке кабинета
function _initOrgUser() {
var u = _getOrgUser();
if (!u) return;
var teamTab = document.getElementById('t-team');
if (teamTab) teamTab.style.display = (u.role==='manager'||u.role==='admin') ? '' : 'none';
_updateSidebarUser();
}
// ── ИЗВЛЕЧЕНИЕ ПАРАМЕТРОВ ДОГОВОРА ───────────────────────────────────────────
function _extractContractParams(contractType) {
var params = {};
// 1. Из загруженного договора (_DEADLINES и contracts store)
var contracts = _getContracts();
if (contracts.length) {
var c = contracts.find(function(x){ return (x.type||'').toLowerCase().includes(contractType); }) || contracts[0];
if (c) {
// Пробуем вытащить суммы из preview
var text = (c.preview || '') + ' ' + (c.type || '');
var amtMatch = text.match(/(\d[\d\s]{3,})\s*(?:руб|₽)/i);
if (amtMatch) params.amount = parseInt(amtMatch[1].replace(/\s/g,''));
}
}
// 2. Из истории чата — ищем суммы
var history = _chatHistory.slice(-10).map(function(m){ return m.content||''; }).join(' ');
var deposits = history.match(/депозит[а-я\s]*(\d[\d\s]+)\s*(?:руб|₽|тыс|к₽)/i);
if (deposits) params.deposit = parseInt(deposits[1].replace(/\s/g,'')) * (history.match(/тыс|к₽/) ? 1000 : 1);
var days_match = history.match(/(\d+)\s*(?:день|дней|дня)/i);
if (days_match) params.days = parseInt(days_match[1]);
// 3. Из активных дедлайнов
if (typeof _DEADLINES !== 'undefined' && _DEADLINES.length) {
var dl = _DEADLINES[0];
if (dl && dl.date) {
var diff = Math.round((new Date(dl.date) - new Date()) / 86400000);
params.days = Math.abs(diff);
}
}
// 4. Из b2b-реквизитов
try {
var b2b = JSON.parse(localStorage.getItem('zashita_b2b') || 'null');
if (b2b && b2b.amount) params.amount = b2b.amount;
} catch(e){}
// Дефолты по типу
if (!params.amount) {
var defaults = { аренда: 80000, подряд: 500000, 'купля-продажа': 300000, трудовой: 60000, займ: 100000, дду: 5000000 };
params.amount = defaults[contractType] || 100000;
}
if (!params.deposit && contractType === 'аренда') params.deposit = params.amount;
return params;
}
// ── КАРТА ДЕЛА ───────────────────────────────────────────────────────────────
var _CASE_NOTES_KEY = 'zashita_case_notes';
// Добавить заметку в карту дела
function _addCaseNote(type, text, meta) {
try {
var notes = JSON.parse(localStorage.getItem(_CASE_NOTES_KEY) || '[]');
notes.push({
id: Date.now(),
ts: new Date().toISOString(),
type: type, // 'risk' | 'missing_doc' | 'promise' | 'decision' | 'refusal' | 'acknowledged'
text: text,
meta: meta || {}
});
localStorage.setItem(_CASE_NOTES_KEY, JSON.stringify(notes));
} catch(e){}
}
function _getCaseNotes() {
try { return JSON.parse(localStorage.getItem(_CASE_NOTES_KEY) || '[]'); }
catch(e) { return []; }
}
function renderCaseMap() {
var el = document.getElementById('casemap-content');
if (!el) return;
var notes = _getCaseNotes();
var dossier = _getDossier() || {};
if (!notes.length && !dossier.facts && !dossier.decisions && !dossier.open) {
el.innerHTML = '<div style="text-align:center;padding:32px;color:var(--mut)">Начните работу с Еленой — карта дела заполнится автоматически.</div>';
return;
}
var today = new Date().toLocaleDateString('ru-RU', {day:'2-digit', month:'long', year:'numeric'});
var html = '<div style="font-size:12px;color:var(--mut);margin-bottom:20px">Сформировано: ' + today + '</div>';
// Секция: Принятые решения (из досье)
if (dossier.decisions && dossier.decisions.length) {
html += _cmSection('✅ Принятые решения', '#16a34a', dossier.decisions.map(function(d){
return {text: d, icon: '✅'};
}));
}
// Секция: Зафиксированные риски
var risks = notes.filter(function(n){ return n.type === 'risk' || n.type === 'acknowledged'; });
if (risks.length) {
html += _cmSection('⚠️ Принятые риски (клиент ознакомлен)', '#d97706', risks.map(function(n){
return {text: n.text, icon: n.type === 'acknowledged' ? '👁' : '⚠️', date: n.ts};
}));
}
// Секция: Отсутствующие документы
var missing = notes.filter(function(n){ return n.type === 'missing_doc'; });
if (missing.length) {
html += _cmSection('📁 Документы отсутствуют', '#dc2626', missing.map(function(n){
return {text: n.text, icon: '❌', sub: n.meta.reason || '', date: n.ts};
}));
}
// Секция: Обещания загрузить
var promises = notes.filter(function(n){ return n.type === 'promise'; });
if (promises.length) {
html += _cmSection('🕐 Обещано загрузить позже', '#2563eb', promises.map(function(n){
return {text: n.text, icon: '📋', date: n.ts};
}));
}
// Секция: Отказы контрагента
var refusals = notes.filter(function(n){ return n.type === 'refusal'; });
if (refusals.length) {
html += _cmSection('🚫 Отказы контрагента', '#7c3aed', refusals.map(function(n){
return {text: n.text, icon: '🚫', date: n.ts};
}));
}
// Секция: Открытые вопросы
if (dossier.open && dossier.open.length) {
html += _cmSection('❓ Открытые вопросы', '#6b7280', dossier.open.map(function(d){
return {text: d, icon: '❓'};
}));
}
// Подпись
html += '<div style="margin-top:24px;padding-top:16px;border-top:1px solid var(--line);' +
'font-size:11px;color:var(--mut);text-align:center">' +
'Карта дела сформирована сервисом ЗАЩИТА (@wasrusgen1) · i@wasrusgen.ru · ' + today +
'</div>';
el.innerHTML = html;
}
function _cmSection(title, color, items) {
if (!items || !items.length) return '';
var html = '<div style="margin-bottom:20px">' +
'<div style="font-weight:700;font-size:14px;color:' + color + ';margin-bottom:8px;' +
'padding-bottom:6px;border-bottom:2px solid ' + color + '20">' + title + '</div>' +
'<div style="display:flex;flex-direction:column;gap:6px">';
items.forEach(function(item) {
var dateStr = item.date ? ' <span style="color:#9ca3af;font-size:11px">' +
new Date(item.date).toLocaleDateString('ru-RU') + '</span>' : '';
html += '<div style="padding:8px 12px;background:#f9fafb;border-radius:8px;border-left:3px solid ' + color + '">' +
'<span style="margin-right:6px">' + item.icon + '</span>' +
'<span style="font-size:13px">' + item.text + '</span>' + dateStr +
(item.sub ? '<div style="font-size:12px;color:var(--mut);margin-top:3px">' + item.sub + '</div>' : '') +
'</div>';
});
return html + '</div></div>';
}
function _exportCaseMap() {
var el = document.getElementById('casemap-content');
if (!el) return;
var w = window.open('', '_blank');
w.document.write('<html><head><title>Карта дела — ЗАЩИТА</title>' +
'<style>body{font-family:Arial,sans-serif;max-width:700px;margin:40px auto;font-size:13px;color:#1a1a2e;line-height:1.6}' +
'h1{color:#9f1239}h2{font-size:14px}' +
'.section{margin-bottom:20px}.item{padding:8px 12px;background:#f9fafb;border-radius:6px;margin-bottom:4px}' +
'</style></head><body>' +
'<h1>📝 Карта дела</h1>' + el.innerHTML +
'<script>window.print();<\/script></body></html>');
w.document.close();
}
// ── ОПРЕДЕЛЕНИЕ ТИПА ДОГОВОРА ───────────────────────────────────────────────
function _detectContractType(text) {
var t = (text || '').toLowerCase();
if (/аренд|съезж|помещен|цех|офис|склад|депозит/.test(t)) return 'аренда';
if (/подряд|услуг|ремонт|строит|разработ|дизайн|монтаж/.test(t)) return 'подряд';
if (/дду|долев|новостройк|застройщ|214/.test(t)) return 'дду';
if (/квартир|недвижим|вторич|комнат|дом|земл/.test(t)) return 'недвижимость';
if (/трудов|работодат|уволь|зарплат|ип/.test(t)) return 'трудовой';
if (/займ|расписк|долг|одолжил|кредит/.test(t)) return 'займ';
if (/поставк|товар|купли-продаж|покупател|продавец/.test(t)) return 'купля-продажа';
if (/риелтор|агентств|комисси|эксклюзив/.test(t)) return 'агент';
return null;
}
// ── АУДИТ ДОКУМЕНТОВ В ЧАТЕ ─────────────────────────────────────────────────
function _showDocAuditInChat(contractType, intent) {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
var cl = DOC_CHECKLISTS[contractType];
if (!cl) {
// Тип не определён — спрашиваем
var ask = document.createElement('div');
ask.className = 'hc-msg hc-elena';
ask.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble">Уточните — что за договор у Вас: аренда, подряд, купля-продажа, трудовой или что-то другое?</div>';
msgs.appendChild(ask);
_rcShowControls();
return;
}
// Загружаем сохранённые отметки
var saved = {};
try { saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + contractType) || '{}'); } catch(e){}
var critical = cl.docs.filter(function(d){ return !saved[d.id] && d.risk === 'critical'; });
var high = cl.docs.filter(function(d){ return !saved[d.id] && d.risk === 'high'; });
var all = cl.docs.length;
var have = Object.keys(saved).filter(function(k){ return saved[k]; }).length;
// Вводная от Елены
var intro = document.createElement('div');
intro.className = 'hc-msg hc-elena';
var introText = critical.length
? 'Сейчас проверим документы по Вашей ситуации. Сразу вижу ' + critical.length + ' критических пункта — это важно для защиты Ваших интересов.'
: 'Давайте быстро проверим документы — это займёт минуту.';
intro.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg"><div class="hc-bubble">' + introText + '</div>';
msgs.appendChild(intro);
// Чеклист в пузыре
setTimeout(function(){
var checkDiv = document.createElement('div');
checkDiv.className = 'hc-msg hc-elena';
var docsHtml = '<div style="margin-bottom:8px;font-size:13px;color:var(--mut)">' + cl.icon + ' ' + cl.label + ' — отметьте что есть:</div>';
docsHtml += '<div style="display:flex;flex-direction:column;gap:6px">';
cl.docs.forEach(function(doc) {
var checked = !!saved[doc.id];
var riskBadge = {critical:'🔴', high:'🟠', medium:'🟡'}[doc.risk] || '⚪';
docsHtml +=
'<label style="display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;cursor:pointer;' +
'background:' + (checked ? '#f0fdf4' : '#fafafa') + ';' +
'border:1px solid ' + (checked ? '#86efac' : '#e5e7eb') + '">' +
'<input type="checkbox" ' + (checked ? 'checked' : '') + ' style="width:15px;height:15px;accent-color:#16a34a" ' +
'onchange="_auditCheck(\'' + contractType + '\',\'' + doc.id + '\',this.checked,\'' + intent + '\')">' +
'<span style="flex:1;font-size:13px' + (checked ? ';color:#15803d' : '') + '">' + doc.label + '</span>' +
'<span style="font-size:11px">' + riskBadge + '</span>' +
'</label>';
});
docsHtml += '</div>';
docsHtml += '<div style="margin-top:10px;font-size:12px;color:var(--mut)">Отметьте всё что есть — покажу риски по каждому пропуску</div>';
checkDiv.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg"><div class="hc-bubble" style="max-width:500px">' + docsHtml + '</div>';
msgs.appendChild(checkDiv);
msgs.scrollTop = msgs.scrollHeight;
// Если уже есть пропуски — сразу показываем что критично
if (critical.length) {
setTimeout(function(){ _showAuditGaps(contractType, intent); }, 1000);
} else {
_rcShowControls();
}
}, 600);
}
function _auditCheck(contractType, docId, checked, intent) {
try {
var saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + contractType) || '{}');
saved[docId] = checked;
localStorage.setItem('zashita_doccheck_' + contractType, JSON.stringify(saved));
} catch(e){}
if (!checked) {
// Документ сняли с галочки → показываем варианты
var cl = DOC_CHECKLISTS[contractType] || {docs:[]};
var doc = cl.docs.find(function(d){ return d.id === docId; });
if (doc) {
setTimeout(function(){ _showMissingDocOptions(contractType, doc, intent); }, 200);
return;
}
}
setTimeout(function(){ _showAuditGaps(contractType, intent); }, 300);
}
// Показывает варианты поведения когда документа нет
function _showMissingDocOptions(contractType, doc, intent) {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
// Удаляем старый блок вариантов
var old = document.getElementById('missing-doc-options');
if (old) old.remove();
var riskColor = {critical:'#dc2626', high:'#d97706', medium:'#2563eb'}[doc.risk] || '#6b7280';
var div = document.createElement('div');
div.id = 'missing-doc-options';
div.className = 'hc-msg hc-elena';
var html =
'<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble" style="max-width:480px">' +
'<div style="font-size:13px;margin-bottom:8px">' +
'<b>' + doc.label + '</b> — ' + doc.tip +
'</div>' +
'<div style="font-size:12px;color:var(--mut);margin-bottom:10px">Что сейчас с этим документом?</div>' +
'<div style="display:flex;flex-direction:column;gap:6px">' +
// Вариант 1: Нет, никогда не было
'<button style="text-align:left;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;background:#fafafa;cursor:pointer;font-size:12px;font-family:inherit" ' +
'onclick="_handleMissingDoc(\'' + contractType + '\',\'' + doc.id + '\',\'never\',\'' + intent + '\')">' +
'❌ <b>Нет</b> — не было и нет' +
'</button>' +
// Вариант 2: Есть, но не под рукой
'<button style="text-align:left;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;background:#fafafa;cursor:pointer;font-size:12px;font-family:inherit" ' +
'onclick="_handleMissingDoc(\'' + contractType + '\',\'' + doc.id + '\',\'later\',\'' + intent + '\')">' +
'📁 <b>Есть</b> — найду и догружу позже' +
'</button>' +
// Вариант 3: Не уверен
'<button style="text-align:left;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;background:#fafafa;cursor:pointer;font-size:12px;font-family:inherit" ' +
'onclick="_handleMissingDoc(\'' + contractType + '\',\'' + doc.id + '\',\'unknown\',\'' + intent + '\')">' +
'🤔 <b>Не уверен</b> — надо проверить' +
'</button>' +
// Вариант 4: Утеряно
'<button style="text-align:left;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;background:#fafafa;cursor:pointer;font-size:12px;font-family:inherit" ' +
'onclick="_handleMissingDoc(\'' + contractType + '\',\'' + doc.id + '\',\'lost\',\'' + intent + '\')">' +
'🗑 <b>Утеряно</b> — не найти' +
'</button>' +
// Вариант 5: Вторая сторона не даёт
'<button style="text-align:left;padding:8px 12px;border:1.5px solid #fca5a5;border-radius:8px;background:#fff5f5;cursor:pointer;font-size:12px;font-family:inherit" ' +
'onclick="_handleMissingDoc(\'' + contractType + '\',\'' + doc.id + '\',\'refused\',\'' + intent + '\')">' +
'🚫 <b>Отказали</b> — контрагент не даёт' +
'</button>' +
'</div></div>';
div.innerHTML = html;
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
}
// Реагирует на выбранный вариант
function _handleMissingDoc(contractType, docId, variant, intent) {
// Убираем блок вариантов
var opt = document.getElementById('missing-doc-options');
if (opt) opt.remove();
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
// Пузырь клиента
var labels = {
never: '❌ Нет — не было и нет',
later: '📁 Есть — найду позже',
unknown: '🤔 Не уверен',
lost: '🗑 Утеряно',
refused: '🚫 Контрагент не даёт'
};
var uDiv = document.createElement('div');
uDiv.className = 'hc-msg hc-user';
uDiv.innerHTML = '<div class="hc-bubble">' + (labels[variant] || variant) + '</div>';
msgs.appendChild(uDiv);
// Сохраняем статус + пишем в карту дела
try {
var statKey = 'zashita_docstatus_' + contractType;
var statuses = JSON.parse(localStorage.getItem(statKey) || '{}');
statuses[docId] = variant;
localStorage.setItem(statKey, JSON.stringify(statuses));
} catch(e){}
var noteTypeMap = { never:'missing_doc', later:'promise', unknown:'missing_doc', lost:'missing_doc', refused:'refusal' };
_addCaseNote(noteTypeMap[variant] || 'missing_doc',
doc.label + ' — ' + (labels[variant] || variant),
{ docId: docId, contractType: contractType, reason: labels[variant] });
// Ответ Елены через API
var cl = DOC_CHECKLISTS[contractType] || {docs:[]};
var doc = cl.docs.find(function(d){ return d.id === docId; }) || {};
var promptMap = {
never: 'Клиент сказал что документа "' + doc.label + '" никогда не было. Объясни риск и предложи два варианта выхода из ситуации (составить/компенсировать другими доказательствами).',
later: 'Клиент сказал что документ "' + doc.label + '" есть, но не под рукой — найдёт позже. Зафикисруй это, скажи когда он понадобится и продолжи работу.',
unknown: 'Клиент не знает есть ли документ "' + doc.label + '". Объясни как он должен выглядеть и где его искать. Дай альтернативу если не найдёт.',
lost: 'Клиент потерял документ "' + doc.label + '". Объясни как получить копию (у контрагента / нотариуса) и какие альтернативные доказательства подойдут.',
refused: 'Контрагент отказывается дать документ "' + doc.label + '" клиенту. Объясни права клиента и предложи официальный запрос + судебное истребование.'
};
// Для варианта "позже" — сразу показываем выбор даты напоминания
if (variant === 'later') {
setTimeout(function(){ _showReminderPicker(doc, contractType, intent); }, 300);
return;
}
setTimeout(function(){
_rcAddTyping();
_elenaApi(promptMap[variant] || promptMap.never, intent, function(apiReply, apiActions){
_rcRemoveTyping();
var reply = apiReply || 'Понятно. Давайте разберём что можно сделать в этой ситуации.';
_chatHistory.push({role:'user', content: labels[variant]});
_chatHistory.push({role:'assistant', content: reply});
_saveHistory();
_rcAddBubble(reply, false);
if (apiActions && apiActions.length) _renderElenaActions(apiActions, msgs);
_rcShowControls();
});
}, 300);
}
// ── НАПОМИНАНИЯ ──────────────────────────────────────────────────────────────
var _REMINDERS_KEY = 'zashita_reminders';
function _saveReminder(docLabel, docId, contractType, remindAt) {
try {
var reminders = JSON.parse(localStorage.getItem(_REMINDERS_KEY) || '[]');
// Удаляем старый если был
reminders = reminders.filter(function(r){ return !(r.docId === docId && r.contractType === contractType); });
reminders.push({
id: Date.now(), docId: docId, contractType: contractType,
docLabel: docLabel, remindAt: remindAt, done: false,
createdAt: new Date().toISOString()
});
localStorage.setItem(_REMINDERS_KEY, JSON.stringify(reminders));
} catch(e){}
}
function _getOverdueReminders() {
try {
var reminders = JSON.parse(localStorage.getItem(_REMINDERS_KEY) || '[]');
var now = Date.now();
return reminders.filter(function(r){ return !r.done && new Date(r.remindAt).getTime() <= now; });
} catch(e){ return []; }
}
function _showReminderPicker(doc, contractType, intent) {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
var now = new Date();
var dates = [
{ label: 'Сегодня вечером', val: _dateOffset(0, 18) },
{ label: 'Завтра утром', val: _dateOffset(1, 9) },
{ label: 'Через 3 дня', val: _dateOffset(3, 9) },
{ label: 'Через неделю', val: _dateOffset(7, 9) },
];
var savedContact = '';
try { var b2b = JSON.parse(localStorage.getItem('zashita_b2b')||'null'); if(b2b&&b2b.phone) savedContact=b2b.phone; } catch(e){}
var div = document.createElement('div');
div.className = 'hc-msg hc-elena';
div.innerHTML =
'<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble">' +
'<div style="font-size:13px;margin-bottom:10px">' +
'Хорошо! Когда напомнить загрузить <b>' + doc.label + '</b>?' +
'</div>' +
// Поле контакта
'<div style="margin-bottom:12px;padding:10px;background:#f0f9ff;border-radius:8px">' +
'<div style="font-size:12px;font-weight:600;margin-bottom:6px">📲 Куда прислать напоминание?</div>' +
'<input id="reminder-contact" class="elena-main-inp" placeholder="Телефон +7... или Telegram @username" ' +
'value="' + savedContact + '" style="font-size:12px;margin-bottom:6px">' +
'<label style="display:flex;align-items:flex-start;gap:6px;font-size:11px;color:var(--mut);cursor:pointer">' +
'<input type="checkbox" id="reminder-consent" style="margin-top:2px;width:13px;height:13px" checked>' +
'<span>Согласен на обработку контактных данных <b>исключительно для направления напоминаний</b> ' +
'по данному делу. Без использования в маркетинге. ' +
'<a href="privacy.html" target="_blank" style="color:var(--bg)">Политика конфиденциальности</a>' +
' · <a href="https://reestr.rkn.gov.ru/" target="_blank" style="color:var(--bg)">Реестр операторов РКН</a></span>' +
'</label>' +
'</div>' +
'<div style="display:flex;flex-direction:column;gap:6px">' +
dates.map(function(d){
return '<button style="text-align:left;padding:8px 12px;border:1.5px solid var(--line);' +
'border-radius:8px;background:#fafafa;cursor:pointer;font-size:12px;font-family:inherit" ' +
'onclick="_setReminder(\'' + doc.id + '\',\'' + doc.label.replace(/'/g,"\\'") + '\',\'' +
contractType + '\',\'' + d.val + '\',\'' + intent + '\')">' +
'🔔 ' + d.label + ' <span style="color:var(--mut);font-size:11px">(' + _formatDate(d.val) + ')</span>' +
'</button>';
}).join('') +
'<div style="display:flex;gap:8px;align-items:center;margin-top:4px">' +
'<input type="date" id="reminder-custom-date" style="border:1.5px solid var(--line);border-radius:8px;' +
'padding:7px 10px;font-size:12px;font-family:inherit;flex:1" min="' + now.toISOString().slice(0,10) + '">' +
'<button class="btn btn-o" style="padding:7px 12px;font-size:12px" ' +
'onclick="var d=document.getElementById(\'reminder-custom-date\').value;if(d)_setReminder(\'' +
doc.id + '\',\'' + doc.label.replace(/'/g,"\\'") + '\',\'' + contractType + '\',d+\'T09:00\',\'' + intent + '\')">' +
'Выбрать</button>' +
'</div>' +
'</div>' +
'</div>';
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
}
function _dateOffset(days, hour) {
var d = new Date();
d.setDate(d.getDate() + days);
d.setHours(hour, 0, 0, 0);
return d.toISOString().slice(0, 16);
}
function _formatDate(isoStr) {
var d = new Date(isoStr);
return d.toLocaleDateString('ru-RU', {day:'numeric', month:'short', hour:'2-digit', minute:'2-digit'});
}
function _setReminder(docId, docLabel, contractType, remindAt, intent) {
var msgs = document.getElementById('rchat-msgs');
var last = msgs ? msgs.lastElementChild : null;
if (last && last.className.includes('hc-elena')) last.remove();
_saveReminder(docLabel, docId, contractType, remindAt);
_addCaseNote('promise', docLabel + ' — напоминание установлено на ' + _formatDate(remindAt),
{ docId: docId, contractType: contractType, remindAt: remindAt });
// Сохраняем контакт на сервер если указан и есть согласие
var contactInp = document.getElementById('reminder-contact');
var consentInp = document.getElementById('reminder-consent');
var contactVal = contactInp ? contactInp.value.trim() : '';
var hasConsent = consentInp ? consentInp.checked : false;
if (contactVal && hasConsent) {
var ctx = _buildElenaContext();
fetch(API_BASE + '/api/contact', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
phone: contactVal.startsWith('@') || contactVal.includes('t.me') ? '' : contactVal,
telegram: contactVal.startsWith('@') || contactVal.includes('t.me') ? contactVal : '',
name: ctx.client_name || '',
case_context: 'Напоминание: ' + docLabel + ' до ' + remindAt,
consent: true,
purpose: ['reminder'], // ← ст.22 ч.2 п.1 152-ФЗ: только напоминания = уведомление РКН не нужно
source: 'reminder_picker'
})
}).then(function(r){ return r.json(); }).then(function(d){
if (d.saved) {
// Сохраняем в localStorage для следующих форм
try {
var b2b = JSON.parse(localStorage.getItem('zashita_b2b') || '{}');
if (d.type === 'telegram') b2b.telegram = contactVal;
else b2b.phone = contactVal;
localStorage.setItem('zashita_b2b', JSON.stringify(b2b));
} catch(e){}
toast('✅ Контакт сохранён — пришлём напоминание ' + _formatDate(remindAt));
}
}).catch(function(){});
}
// Пузырь клиента
if (msgs) {
var uDiv = document.createElement('div');
uDiv.className = 'hc-msg hc-user';
uDiv.innerHTML = '<div class="hc-bubble">🔔 Напомни ' + _formatDate(remindAt) + '</div>';
msgs.appendChild(uDiv);
}
// Ответ Елены
setTimeout(function(){
_rcAddTyping();
var prompt = 'Клиент установил напоминание загрузить документ "' + docLabel +
'" на ' + _formatDate(remindAt) + '. Зафиксируй и скажи что будет видно при следующем входе. Продолжаем работу.';
_elenaApi(prompt, intent, function(apiReply) {
_rcRemoveTyping();
var reply = apiReply || 'Зафиксировала. ' + _formatDate(remindAt) + ' напомню. Продолжаем.';
_rcAddBubble(reply, false);
_rcShowControls();
});
}, 300);
}
// Проверка при старте — есть ли просроченные напоминания
function _checkRemindersOnStart() {
var overdue = _getOverdueReminders();
if (!overdue.length) return;
// Показываем через 1 сек после initReturnChat
setTimeout(function(){
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
overdue.forEach(function(r) {
var div = document.createElement('div');
div.className = 'hc-msg hc-elena';
div.innerHTML =
'<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble">' +
'<div style="font-weight:700;margin-bottom:8px">🔔 Напоминание</div>' +
'<div style="font-size:13px;margin-bottom:10px">' +
'Вы обещали загрузить <b>' + r.docLabel + '</b>. Удалось найти?' +
'</div>' +
'<div style="display:flex;gap:8px;flex-wrap:wrap">' +
'<button class="btn btn-p" style="padding:7px 14px;font-size:12px" ' +
'onclick="_reminderDone(\'' + r.id + '\',true)">✅ Загрузил — отмечаю</button>' +
'<button class="btn btn-o" style="padding:7px 14px;font-size:12px" ' +
'onclick="_setReminder(\'' + r.docId + '\',\'' + r.docLabel.replace(/'/g,"\\'") + '\',\'' + r.contractType + '\',\'' + _dateOffset(1,9) + '\',\'question\')">' +
'🔔 Перенести на завтра</button>' +
'<button class="svc-btn-detail" style="font-size:12px" ' +
'onclick="_reminderDone(\'' + r.id + '\',false)">❌ Не буду — зафиксировать отказ</button>' +
'</div>' +
'</div>';
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
});
}, 1200);
}
function _reminderDone(reminderId, uploaded) {
// Убираем пузырь
var msgs = document.getElementById('rchat-msgs');
var last = msgs ? msgs.lastElementChild : null;
if (last) last.remove();
try {
var reminders = JSON.parse(localStorage.getItem(_REMINDERS_KEY) || '[]');
var r = reminders.find(function(x){ return x.id == reminderId; });
if (r) {
r.done = true;
r.outcome = uploaded ? 'uploaded' : 'refused';
localStorage.setItem(_REMINDERS_KEY, JSON.stringify(reminders));
if (uploaded) {
// Отмечаем документ в чеклисте как загруженный
try {
var saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + r.contractType) || '{}');
saved[r.docId] = true;
localStorage.setItem('zashita_doccheck_' + r.contractType, JSON.stringify(saved));
} catch(e){}
_addCaseNote('decision', r.docLabel + ' — загружен (подтверждено)', { reminderId: reminderId });
if (msgs) { var ok = document.createElement('div'); ok.className='hc-msg hc-user'; ok.innerHTML='<div class="hc-bubble">✅ Загрузил</div>'; msgs.appendChild(ok); }
toast('✅ Зафиксировано — документ получен');
} else {
// Конвертируем обещание в отказ
_addCaseNote('missing_doc', r.docLabel + ' — клиент решил не загружать (зафиксирован отказ)', { final: true });
if (msgs) { var no = document.createElement('div'); no.className='hc-msg hc-user'; no.innerHTML='<div class="hc-bubble">❌ Не буду загружать</div>'; msgs.appendChild(no); }
toast('📝 Зафиксировано в карте дела как окончательный отказ');
}
}
} catch(e){}
_rcShowControls();
}
function _showAuditGaps(contractType, intent) {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
var cl = DOC_CHECKLISTS[contractType];
if (!cl) return;
var saved = {};
try { saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + contractType) || '{}'); } catch(e){}
var missing = cl.docs.filter(function(d){ return !saved[d.id]; });
var critical = missing.filter(function(d){ return d.risk === 'critical'; });
// Удаляем старый gap-блок
var old = document.getElementById('audit-gaps');
if (old) old.remove();
if (!missing.length) {
var okDiv = document.createElement('div');
okDiv.id = 'audit-gaps';
okDiv.className = 'hc-msg hc-elena';
okDiv.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble"><div style="color:#16a34a;font-weight:700;margin-bottom:6px">✅ Отличный пакет документов!</div>' +
'Всё что нужно — в наличии. Можем сразу перейти к разбору ситуации.</div>';
msgs.appendChild(okDiv);
_rcShowControls();
return;
}
var gapDiv = document.createElement('div');
gapDiv.id = 'audit-gaps';
gapDiv.className = 'hc-msg hc-elena';
var gapsHtml = '';
if (critical.length) {
gapsHtml += '<div style="font-weight:700;color:#dc2626;margin-bottom:8px">⚠️ Критические пробелы:</div>';
critical.forEach(function(d){
gapsHtml += '<div style="background:#fef2f2;border-left:3px solid #dc2626;border-radius:0 8px 8px 0;padding:8px 10px;margin-bottom:6px;font-size:13px">' +
'<b>' + d.label + '</b><br><span style="color:#6b7280">' + d.tip + '</span></div>';
});
}
var nonCrit = missing.filter(function(d){ return d.risk !== 'critical'; });
if (nonCrit.length && !critical.length) {
gapsHtml += '<div style="font-weight:600;margin-bottom:8px">Рекомендую получить:</div>';
nonCrit.forEach(function(d){
var c = d.risk === 'high' ? '#d97706' : '#2563eb';
gapsHtml += '<div style="border-left:3px solid ' + c + ';padding:6px 10px;margin-bottom:4px;font-size:13px;color:#374151">' + d.label + '</div>';
});
}
// ── РАСЧЁТ УЩЕРБА INLINE ──
// Запускаем /api/estimate чтобы показать финансовый риск прямо в блоке
var _contractParams = _extractContractParams(contractType);
var _situationText = _chatHistory.slice(-6).map(function(m){ return m.content||''; }).join(' ');
fetch(API_BASE + '/api/estimate', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
situation: _situationText,
contract_type: contractType,
contract_params: _contractParams,
history: _chatHistory.slice(-4)
})
})
.then(function(r){ return r.json(); })
.then(function(est) {
if (!est.damage_min && !est.damage_max) return;
var dmgDiv = document.getElementById('audit-damage');
if (dmgDiv) dmgDiv.remove();
var dmg = document.createElement('div');
dmg.id = 'audit-damage';
dmg.className = 'hc-msg hc-elena';
var minF = (est.damage_min||0).toLocaleString('ru');
var maxF = (est.damage_max||0).toLocaleString('ru');
dmg.innerHTML =
'<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble" style="max-width:480px">' +
'<div style="font-size:12px;color:var(--mut);margin-bottom:6px">Финансовый риск при текущем состоянии документов:</div>' +
'<div style="background:#fef2f2;border:1.5px solid #fca5a5;border-radius:10px;padding:12px 16px;margin-bottom:10px">' +
'<div style="font-size:12px;color:#6b7280;margin-bottom:2px">Примерный ущерб</div>' +
'<div style="font-family:var(--font-logo,\'Montserrat\');font-size:22px;font-weight:900;color:#dc2626">' +
minF + ' — ' + maxF + ' ₽' +
'</div>' +
(est.damage_comment ? '<div style="font-size:11px;color:#6b7280;margin-top:4px">' + est.damage_comment + '</div>' : '') +
(est.damage_formula ? '<div style="font-size:11px;color:#9ca3af;margin-top:2px">Формула: ' + est.damage_formula + '</div>' : '') +
'</div>' +
(est.summary ? '<div style="font-size:13px;color:#374151">' + est.summary + '</div>' : '') +
'</div>';
var msgs2 = document.getElementById('rchat-msgs');
if (msgs2) { msgs2.appendChild(dmg); dmg.scrollIntoView({behavior:'smooth'}); }
// Сохраняем в карту дела
_addCaseNote('risk', 'Расчётный ущерб: ' + minF + '' + maxF + '₽ · ' + (est.damage_comment||''), {contractType: contractType});
// После показа ущерба — оффер услуги
setTimeout(function(){ _checkAndOfferService(msgs2); }, 1500);
})
.catch(function(){});
// Кнопки: исправить + ПРИНЯТЬ РИСКИ И ПРОДОЛЖИТЬ
gapsHtml += '<div style="margin-top:14px">';
if (critical.length) {
gapsHtml += '<div style="font-size:12px;color:#6b7280;margin-bottom:8px">' +
'Хотите устранить пробелы — или продолжить с тем что есть?</div>';
gapsHtml += '<div style="display:flex;gap:8px;flex-wrap:wrap">' +
'<button class="btn btn-p" style="padding:7px 14px;font-size:12px" ' +
'onclick="_offerDocFix(\'' + contractType + '\',\'' + intent + '\')">🛠 Устранить пробелы</button>' +
'<button class="btn btn-o" style="padding:7px 14px;font-size:12px" ' +
'onclick="_acknowledgeRisksAndProceed(\'' + contractType + '\',\'' + intent + '\')">✅ Понимаю риски — продолжаем</button>' +
'</div>';
} else {
// Только некритичные — сразу предлагаем двигаться
gapsHtml += '<button class="btn btn-p" style="padding:7px 16px;font-size:13px" ' +
'onclick="_acknowledgeRisksAndProceed(\'' + contractType + '\',\'' + intent + '\')">▶ Продолжаем</button>' +
'<button class="svc-btn-detail" style="font-size:12px;margin-left:8px" ' +
'onclick="_offerDocFix(\'' + contractType + '\',\'' + intent + '\')">🛠 Устранить пробелы</button>';
}
gapsHtml += '</div>';
gapDiv.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg"><div class="hc-bubble" style="max-width:480px">' + gapsHtml + '</div>';
msgs.appendChild(gapDiv);
msgs.scrollTop = msgs.scrollHeight;
_rcShowControls();
}
// Клиент принял риски → фиксируем и показываем оффер услуги
function _acknowledgeRisksAndProceed(contractType, intent) {
var old = document.getElementById('audit-gaps');
if (old) old.remove();
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
// Пузырь клиента
var uDiv = document.createElement('div');
uDiv.className = 'hc-msg hc-user';
uDiv.innerHTML = '<div class="hc-bubble">Понимаю риски — продолжаем</div>';
msgs.appendChild(uDiv);
// Сохраняем факт подтверждения в досье + карту дела
_updateDossier({ decisions: ['Клиент подтвердил осведомлённость о рисках по ' + contractType + ' и продолжил'] });
_addCaseNote('acknowledged', 'Клиент ознакомлен с рисками по договору (' + contractType + ') и подтвердил готовность продолжать', { contractType: contractType });
_chatHistory.push({role: 'user', content: 'Понимаю риски — продолжаем'});
_saveHistory();
// Елена: фиксирует и переходит к оказанию услуги
setTimeout(function(){
_rcAddTyping();
_elenaApi(
'Клиент ознакомлен с рисками по ' + contractType + ' и подтвердил готовность продолжать. ' +
'Зафиксируй это, скажи что именно можешь сделать прямо сейчас и предложи начать.',
intent,
function(apiReply, apiActions) {
_rcRemoveTyping();
var reply = apiReply ||
'Зафиксировала. Работаем с тем что есть — это позволяет двигаться вперёд. ' +
'Давайте начнём с самого важного для вашей ситуации.';
_chatHistory.push({role:'assistant', content: reply});
_saveHistory();
_rcAddBubble(reply, false);
if (apiActions && apiActions.length) _renderElenaActions(apiActions, msgs);
// Небольшая пауза → аудит документов (Блок Д) → потом оффер
var detectedType = _detectContractType(ctx + ' ' + reply);
setTimeout(function(){
if (detectedType) {
_showDocAuditInChat(detectedType, intent);
} else {
var wrap2 = document.querySelector('.chatwrap') || msgs;
_checkAndOfferService(wrap2);
}
}, 1000);
_rcShowControls();
});
}, 400);
}
function _offerDocFix(contractType, intent) {
var msgs = document.getElementById('rchat-msgs');
if (!msgs) return;
var saved = {};
try { saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + contractType) || '{}'); } catch(e){}
var cl = DOC_CHECKLISTS[contractType] || {docs:[]};
var critical = cl.docs.filter(function(d){ return !saved[d.id] && d.risk === 'critical'; });
var fix = document.createElement('div');
fix.className = 'hc-msg hc-elena';
var fixes = '';
critical.forEach(function(d) {
var canGenerate = /уведомлени|акт|претензи|расписк/.test(d.label.toLowerCase());
fixes += '<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--line)">' +
'<div style="font-size:13px">' + d.label + '</div>' +
(canGenerate
? '<button class="btn btn-p" style="padding:5px 10px;font-size:11px;white-space:nowrap" ' +
'onclick="_startTemplate(\'' + _docToTemplate(d.id, contractType) + '\')">📝 Составить</button>'
: '<span style="font-size:11px;color:var(--mut)">запросить у стороны</span>') +
'</div>';
});
fix.innerHTML = '<img class="hc-av" src="logos/elena-photo.jpg">' +
'<div class="hc-bubble" style="max-width:480px">' +
'<div style="font-weight:600;margin-bottom:10px">Что можно сделать прямо сейчас:</div>' +
fixes + '</div>';
msgs.appendChild(fix);
msgs.scrollTop = msgs.scrollHeight;
}
function _docToTemplate(docId, contractType) {
var map = {
'notice': 'notice_no_renewal',
'act': 'act_acceptance',
'act_out': 'act_acceptance',
'receipt': 'notice_no_renewal',
};
return map[docId] || 'claim_payment';
}
// ── ЧЕКЛИСТ ДОКУМЕНТОВ ──────────────────────────────────────────────────────
var DOC_CHECKLISTS = {
'аренда': {
label: 'Договор аренды', icon: '🏠',
docs: [
{ id:'contract', label:'Договор аренды (подписанный)', risk:'critical', tip:'Нет договора — нет правовых оснований пользования помещением' },
{ id:'act_in', label:'Акт приёмки-передачи при заезде', risk:'critical', tip:'Без акта арендодатель может заявить что ВЫ испортили помещение' },
{ id:'photo_in', label:'Фотофиксация помещения при заезде', risk:'high', tip:'Без фото сложно доказать состояние на дату заезда' },
{ id:'deposit_rec', label:'Квитанция об оплате депозита', risk:'high', tip:'Без квитанции спорно — платили ли депозит и какую сумму' },
{ id:'payments', label:'Квитанции/платёжки об аренде', risk:'medium', tip:'Сложнее доказать факт регулярной оплаты' },
{ id:'notice', label:'Уведомление о непродлении (если нужно)', risk:'critical', tip:'Без уведомления — договор автоматически продлится' },
{ id:'act_out', label:'Акт возврата помещения (при съезде)', risk:'high', tip:'Без акта арендодатель может требовать доплату «за пользование»' },
]
},
'подряд': {
label: 'Договор подряда/услуг', icon: '🔨',
docs: [
{ id:'contract', label:'Договор подряда/услуг', risk:'critical', tip:'Нет договора — нет оснований требовать исполнения' },
{ id:'tz', label:'ТЗ / спецификация работ', risk:'critical', tip:'Без ТЗ любой результат подрядчик назовёт «надлежащим»' },
{ id:'act', label:'Акт сдачи-приёмки работ', risk:'high', tip:'Без акта работы считаются невыполненными' },
{ id:'payments', label:'Платёжки об оплате', risk:'medium', tip:'Нужны для доказательства оплаты' },
{ id:'warranty', label:'Гарантийные обязательства', risk:'high', tip:'Без чётких условий гарантии подрядчик откажет в ремонте' },
]
},
'купля-продажа': {
label: 'Купля-продажа', icon: '🛒',
docs: [
{ id:'contract', label:'Договор купли-продажи', risk:'critical', tip:'Нет договора — нет оснований' },
{ id:'upd', label:'УПД / накладная ТОРГ-12', risk:'high', tip:'Без накладной нельзя доказать факт поставки' },
{ id:'act', label:'Акт приёмки по качеству', risk:'critical', tip:'Без акта принятое считается надлежащим' },
{ id:'payments', label:'Платёжные документы', risk:'medium', tip:'Доказательство оплаты' },
]
},
'дду': {
label: 'ДДУ / новостройка', icon: '🏗️',
docs: [
{ id:'ddu', label:'ДДУ зарегистрированный в Росреестре', risk:'critical', tip:'Без регистрации нет защиты по 214-ФЗ' },
{ id:'payments', label:'Квитанции об оплате по ДДУ', risk:'high', tip:'Нужны для расчёта неустойки' },
{ id:'act', label:'Акт осмотра с замечаниями', risk:'critical', tip:'Без замечаний в акте претензии по дефектам сложнее' },
{ id:'act_in', label:'Акт приёмки-передачи квартиры', risk:'high', tip:'Дата акта = начало гарантийного срока' },
{ id:'notice', label:'Уведомления застройщика о сроках', risk:'medium', tip:'Фиксирует когда было обещано и когда нарушено' },
]
},
'недвижимость': {
label: 'Купля-продажа недвижимости', icon: '🏠',
docs: [
{ id:'egrn', label:'Выписка из ЕГРН (свежая, до 30 дней)', risk:'critical', tip:'Аресты, ипотека, запреты — всё здесь' },
{ id:'osnov', label:'Основание права продавца', risk:'critical', tip:'Откуда у него право — дарение/наследство/купля — возможны оспаривания' },
{ id:'forma9', label:'Справка о зарегистрированных (Ф.9)', risk:'high', tip:'Прописанные после сделки не выписываются автоматически' },
{ id:'spr_sup', label:'Согласие супруга (нотариально)', risk:'critical', tip:'Без согласия сделку оспорит супруг' },
{ id:'zhkh', label:'Справка об отсутствии долгов ЖКХ', risk:'medium', tip:'Чужие долги могут перейти к вам' },
{ id:'contract', label:'ДКП зарегистрированный в Росреестре', risk:'critical', tip:'До регистрации квартира юридически не ваша' },
]
},
'трудовой': {
label: 'Трудовой договор', icon: '💼',
docs: [
{ id:'contract', label:'Трудовой договор', risk:'critical', tip:'Без договора нет доказательств условий труда' },
{ id:'order', label:'Приказ о приёме на работу', risk:'high', tip:'Подтверждение трудовых отношений' },
{ id:'instruc', label:'Должностная инструкция', risk:'high', tip:'Без неё обязанности расширяют как хотят' },
{ id:'payslips', label:'Расчётные листки', risk:'medium', tip:'Доказательство начисленной зарплаты' },
{ id:'ndfl2', label:'Справка 2-НДФЛ', risk:'high', tip:'Без неё компенсации считают от минималки' },
]
},
'займ': {
label: 'Займ / расписка', icon: '💰',
docs: [
{ id:'receipt', label:'Расписка или договор займа', risk:'critical', tip:'Нет расписки — нет доказательств займа' },
{ id:'transfer', label:'Подтверждение передачи денег', risk:'critical', tip:'Без подтверждения должник скажет «не получал»' },
{ id:'percent', label:'Условие о процентах', risk:'medium', tip:'Без пункта о % займ считается беспроцентным' },
{ id:'deadline', label:'Срок возврата', risk:'high', tip:'Без срока — по первому требованию, но сложно взыскать' },
]
},
};
function renderDocChecklist(contractType) {
var el = document.getElementById('docs-checklist');
if (!el) return;
var type = (contractType || '').toLowerCase();
// Определяем ближайший тип
var key = null;
if (/аренд/.test(type)) key = 'аренда';
else if (/подряд|услуг/.test(type)) key = 'подряд';
else if (/купли.продажи|поставк/.test(type)) key = 'купля-продажа';
else if (/дду|долев|новостройк/.test(type)) key = 'дду';
else if (/недвижим|квартир/.test(type)) key = 'недвижимость';
else if (/трудов/.test(type)) key = 'трудовой';
else if (/займ|расписк/.test(type)) key = 'займ';
if (!key) {
el.innerHTML = '<div style="padding:16px;color:var(--mut);font-size:13px">Загрузите договор — Елена определит тип и покажет нужный чеклист.</div>';
return;
}
var saved = {};
try { saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + key) || '{}'); } catch(e){}
var cl = DOC_CHECKLISTS[key];
var missing = cl.docs.filter(function(d){ return !saved[d.id]; });
var critical = missing.filter(function(d){ return d.risk === 'critical'; }).length;
var html = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px">' +
'<span style="font-size:24px">' + cl.icon + '</span>' +
'<div><div style="font-weight:700;font-size:16px">' + cl.label + '</div>' +
(critical > 0
? '<div style="font-size:12px;color:#dc2626">⚠️ ' + critical + ' критических пункта требуют внимания</div>'
: '<div style="font-size:12px;color:#16a34a">✅ Пакет документов в порядке</div>') +
'</div></div>';
html += '<div style="display:flex;flex-direction:column;gap:8px">';
cl.docs.forEach(function(doc) {
var checked = !!saved[doc.id];
var riskColor = {critical:'#dc2626', high:'#d97706', medium:'#2563eb'}[doc.risk] || '#6b7280';
html +=
'<label style="display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1.5px solid ' +
(checked ? '#16a34a' : riskColor) + ';border-radius:10px;cursor:pointer;background:' +
(checked ? '#f0fdf4' : '#fafafa') + '">' +
'<input type="checkbox" ' + (checked ? 'checked' : '') +
' onchange="_toggleDocCheck(\'' + key + '\',\'' + doc.id + '\',this.checked)" style="margin-top:2px;width:16px;height:16px;accent-color:#16a34a">' +
'<div style="flex:1">' +
'<div style="font-size:13px;font-weight:600;color:' + (checked ? '#15803d' : '#1a1a2e') + '">' + doc.label + '</div>' +
(!checked ? '<div style="font-size:12px;color:' + riskColor + ';margin-top:2px">' + doc.tip + '</div>' : '') +
'</div>' +
'<span style="font-size:10px;font-weight:700;color:' + riskColor + ';text-transform:uppercase;white-space:nowrap">' +
({critical:'🔴 крит.', high:'🟠 важно', medium:'🟡 желат.'}[doc.risk] || '') +
'</span>' +
'</label>';
});
html += '</div>';
// Расчёт ущерба по пробелам
if (missing.length && _apiAvailable) {
var params = _extractContractParams(key);
var situation = 'Договор ' + key + '. Отсутствуют документы: ' + missing.map(function(d){ return d.label; }).join(', ');
fetch(API_BASE + '/api/estimate', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ situation: situation, contract_type: key, contract_params: params, history: [] })
})
.then(function(r){ return r.json(); })
.then(function(est) {
if (!est.damage_min) return;
var dmgEl = document.getElementById('checklist-damage');
if (!dmgEl) return;
var minF = (est.damage_min||0).toLocaleString('ru');
var maxF = (est.damage_max||0).toLocaleString('ru');
dmgEl.innerHTML =
'<div style="background:#fef2f2;border:1.5px solid #fca5a5;border-radius:10px;padding:12px 16px;margin-top:12px">' +
'<div style="font-size:11px;color:#6b7280;margin-bottom:3px">Примерный ущерб при текущих пробелах</div>' +
'<div style="font-size:20px;font-weight:900;color:#dc2626;font-family:inherit">' + minF + ' — ' + maxF + ' ₽</div>' +
(est.damage_comment ? '<div style="font-size:11px;color:#6b7280;margin-top:3px">' + est.damage_comment + '</div>' : '') +
'</div>';
}).catch(function(){});
}
html += '<div id="checklist-damage"></div>';
// Кнопка загрузить недостающие
if (missing.length) {
html += '<div style="margin-top:16px;padding:12px;background:#fffbeb;border:1.5px solid #fcd34d;border-radius:10px;font-size:13px">' +
'<b>Не хватает ' + missing.length + ' документа:</b> ' +
missing.map(function(d){ return d.label.split('(')[0].trim(); }).join(', ') + '.<br>' +
'<span style="color:var(--mut)">Загрузите их в Елену — она проверит и зафиксирует в деле.</span>' +
'</div>';
}
el.innerHTML = html;
}
function _toggleDocCheck(key, docId, checked) {
try {
var saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + key) || '{}');
saved[docId] = checked;
localStorage.setItem('zashita_doccheck_' + key, JSON.stringify(saved));
renderDocChecklist(key);
} catch(e){}
}
// ── РЕКВИЗИТЫ — ПОДПИСЬ И ПЕЧАТЬ ────────────────────────────────────────────
function _uploadRequisite(type, input) {
var file = input.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(e) {
var img = new Image();
img.onload = function() {
// Canvas: убираем белый фон (простая threshold-обработка)
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
var d = data.data;
for (var i = 0; i < d.length; i += 4) {
var r = d[i], g = d[i+1], b = d[i+2];
// Белый / светло-серый → прозрачный
if (r > 220 && g > 220 && b > 220) {
d[i+3] = 0;
}
}
ctx.putImageData(data, 0, 0);
var png = canvas.toDataURL('image/png');
localStorage.setItem('zashita_' + type, png);
_showRequisite(type, png);
toast('✅ ' + (type === 'sig' ? 'Подпись' : 'Печать') + ' сохранена');
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
function _showRequisite(type, dataUrl) {
var prev = document.getElementById(type + '-preview');
var imgEl = document.getElementById(type + '-img');
if (prev) prev.style.display = '';
if (imgEl) imgEl.src = dataUrl;
if (type === 'stamp') {
var sel = document.getElementById('stamp-type-sel');
var sizeEl = document.getElementById('stamp-size');
var sizes = {round:'⌀ 40 мм', rect_ip:'38×70 мм', rect_ooo:'38×58 мм', triangle:'35×50 мм'};
var selVal = sel ? sel.value : 'round';
if (sizeEl) sizeEl.textContent = 'Размер: ' + (sizes[selVal] || '');
}
}
function _clearRequisite(type) {
localStorage.removeItem('zashita_' + type);
var prev = document.getElementById(type + '-preview');
if (prev) prev.style.display = 'none';
toast((type === 'sig' ? 'Подпись' : 'Печать') + ' удалена');
}
function _drawSignature() {
var modal = document.getElementById('sig-draw-modal');
if (modal) { modal.style.display = 'flex'; _initSigCanvas(); }
}
var _sigDrawing = false;
function _initSigCanvas() {
var c = document.getElementById('sig-canvas');
if (!c || c._inited) return;
c._inited = true;
var ctx = c.getContext('2d');
ctx.strokeStyle = '#1a1a2e';
ctx.lineWidth = 2;
ctx.lineCap = 'round';
var getPos = function(e) {
var rect = c.getBoundingClientRect();
var src = e.touches ? e.touches[0] : e;
return { x: src.clientX - rect.left, y: src.clientY - rect.top };
};
c.onmousedown = c.ontouchstart = function(e){ e.preventDefault(); _sigDrawing = true; var p = getPos(e); ctx.beginPath(); ctx.moveTo(p.x, p.y); };
c.onmousemove = c.ontouchmove = function(e){ e.preventDefault(); if (!_sigDrawing) return; var p = getPos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); };
c.onmouseup = c.ontouchend = function(){ _sigDrawing = false; };
}
function _clearCanvas() {
var c = document.getElementById('sig-canvas');
if (c) c.getContext('2d').clearRect(0, 0, c.width, c.height);
}
function _saveSigCanvas() {
var c = document.getElementById('sig-canvas');
if (!c) return;
var png = c.toDataURL('image/png');
localStorage.setItem('zashita_sig', png);
_showRequisite('sig', png);
var modal = document.getElementById('sig-draw-modal');
if (modal) modal.style.display = 'none';
toast('✅ Подпись сохранена');
}
function _saveRequisites() {
var fields = ['name','inn','addr','phone','email'];
var data = {};
fields.forEach(function(f){
var el = document.getElementById('req-' + f);
if (el) data[f] = el.value;
});
try { localStorage.setItem('zashita_requisites', JSON.stringify(data)); } catch(e){}
}
function _loadRequisites() {
try {
var data = JSON.parse(localStorage.getItem('zashita_requisites') || '{}');
['name','inn','addr','phone','email'].forEach(function(f){
var el = document.getElementById('req-' + f);
if (el && data[f]) el.value = data[f];
});
} catch(e){}
// Загружаем сохранённые изображения
['sig','stamp'].forEach(function(type){
var saved = localStorage.getItem('zashita_' + type);
if (saved) _showRequisite(type, saved);
});
}
// Флаг памяти — используется для контекстного приветствия (с защитой от race condition)
window._hasMemory = (function() {
try {
var h = (typeof _chatHistory !== 'undefined' ? _chatHistory.length : 0);
var c = (typeof _getContracts === 'function' ? _getContracts().length : 0);
return (h || c) ? { messages: h, contracts: c, hasDossier: !!(typeof _getDossier === 'function' && _getDossier()) } : null;
} catch(e) { return null; }
})();
</script>
</body></html>