mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 22:24:46 +00:00
cabinet: баннер «тестовая версия» + стартовая подсказка «с чего начать» (бренд-цвета, сворачиваются с памятью)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9f1c52bcea
commit
78f6469e12
@ -55,7 +55,7 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
.sv{display:none;flex:1;flex-direction:column;overflow:hidden}
|
||||
.sv.active{display:flex}
|
||||
.hero{background:var(--white);border-bottom:1px solid var(--border);padding:18px 26px;display:flex;align-items:center;gap:16px;flex-shrink:0}
|
||||
.hero-ic{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,var(--dark),var(--primary));display:flex;align-items:center;justify-content:center;font-size:21px;flex-shrink:0}
|
||||
.hero-ic{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,var(--dark),var(--primary));display:flex;align-items:center;justify-content:center;font-size:21px;flex-shrink:0;color:#fff}
|
||||
.hero-tag{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);margin-bottom:3px}
|
||||
.hero-h{font-size:18px;font-weight:700;color:var(--text)}
|
||||
.hero-d{font-size:13px;color:var(--muted);margin-top:2px}
|
||||
@ -63,9 +63,31 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
.btn{font-family:'Inter';font-weight:600;border:none;cursor:pointer;border-radius:10px;display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
|
||||
.btn-p{background:var(--primary);color:#fff;font-size:13px;padding:10px 18px}
|
||||
.btn-p:hover{background:var(--dark)}.btn-p:disabled{opacity:.5;cursor:default}
|
||||
.scroll{flex:1;overflow-y:auto}
|
||||
.scroll{flex:1;overflow-y:auto;overflow-anchor:none}
|
||||
/* Chat */
|
||||
.chat{padding:24px 26px;display:flex;flex-direction:column;gap:14px}
|
||||
/* Спросить Елену — док на этапах 3-5 */
|
||||
.askdock{display:none;border-top:1px solid var(--border);background:var(--white);flex:0 0 auto}
|
||||
.askdock.show{display:block}
|
||||
.askdock-head{display:flex;align-items:center;gap:8px;padding:11px 18px;cursor:pointer;font-size:13px;font-weight:700;color:var(--primary);user-select:none}
|
||||
.askdock-head .ad-sub{font-weight:500;color:#9ca3af;font-size:12px}
|
||||
.askdock-head .ad-chev{margin-left:auto;transition:transform .2s}
|
||||
.askdock.open .ad-chev{transform:rotate(180deg)}
|
||||
.askdock-body{display:none;border-top:1px solid var(--border)}
|
||||
.askdock.open .askdock-body{display:block}
|
||||
.askdock-thread{max-height:240px;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:10px}
|
||||
.askdock-thread:empty{display:none}
|
||||
.askdock-inbar{display:flex;gap:8px;padding:10px 14px;border-top:1px solid var(--border)}
|
||||
.askdock-inbar textarea{flex:1;border:1px solid var(--border);border-radius:10px;padding:9px 12px;font:inherit;font-size:13px;resize:none;max-height:90px;outline:none}
|
||||
.askdock-inbar textarea:focus{border-color:var(--primary)}
|
||||
.am{display:flex;gap:8px;font-size:13px;line-height:1.45}
|
||||
.am .am-av{width:24px;height:24px;border-radius:50%;flex:0 0 24px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff}
|
||||
.am.u{flex-direction:row-reverse}
|
||||
.am.u .am-av{background:#64748b}
|
||||
.am.e .am-av{background:var(--primary)}
|
||||
.am .am-bb{background:#f1f5f9;border-radius:10px;padding:7px 11px;max-width:86%}
|
||||
.am.u .am-bb{background:#e2e8f0}
|
||||
.am-dev{font-size:11px;color:#92400E;background:#FEF3C7;border-radius:6px;padding:4px 8px;margin-top:5px;font-weight:600}
|
||||
.msg{display:flex;gap:10px;max-width:80%}
|
||||
.msg.user{align-self:flex-end;flex-direction:row-reverse}
|
||||
.av{width:30px;height:30px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#fff}
|
||||
@ -172,25 +194,57 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
.exp-d{font-size:13px;color:var(--muted);line-height:1.5;margin-bottom:14px}
|
||||
.exp-btns{display:flex;flex-wrap:wrap;gap:10px}
|
||||
@media print{.sb,.hdr,.pb,.hero,.nav,.exp-bar,.modal,.mic,.inp-bar{display:none!important}.main,.sv,.scroll,.pad{display:block!important;overflow:visible!important;height:auto!important;margin:0!important;padding:0!important}body{background:#fff}}
|
||||
/* ── Мобильный (Telegram на телефоне) ── */
|
||||
.hdr-burger{display:none;background:none;border:none;color:#fff;font-size:22px;cursor:pointer;padding:0 4px;line-height:1;flex-shrink:0}
|
||||
.sb-backdrop{display:none;position:fixed;inset:54px 0 0 0;background:rgba(0,0,0,.55);z-index:40}
|
||||
.sb-backdrop.show{display:block}
|
||||
@media(max-width:560px){.idea-label{display:none}}
|
||||
@media(max-width:680px){
|
||||
.hdr{padding:0 12px;gap:8px}
|
||||
.hdr-burger{display:block}
|
||||
.hdr-t{font-size:12px}
|
||||
.hdr-client{display:none}
|
||||
.elena-nm{display:none}
|
||||
.sb{position:fixed;left:-260px;top:54px;bottom:0;z-index:50;width:240px;transition:left .25s ease;box-shadow:3px 0 18px rgba(0,0,0,.45)}
|
||||
.sb.open{left:0}
|
||||
.main{width:100%}
|
||||
.hero{padding:13px 15px;gap:11px}
|
||||
.hero-ic{width:38px;height:38px;font-size:17px}
|
||||
.hero-h{font-size:16px}
|
||||
.hero-d{font-size:12px}
|
||||
.chat{padding:16px 13px;gap:11px}
|
||||
.msg{max-width:90%}
|
||||
.bb{font-size:13.5px}
|
||||
.inbar{padding:10px 13px;gap:8px}
|
||||
.icon-btn{width:40px;height:40px}
|
||||
.run-card{padding:20px 16px;margin:8px 14px 16px}
|
||||
.exp-bar{padding:16px 14px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="hdr">
|
||||
<div class="hdr-ic">@</div>
|
||||
<div class="hdr-t">wasrusgen1<span class="hdr-sep"></span><b>КОНСАЛТИНГ</b></div>
|
||||
<button class="hdr-burger" onclick="toggleSb()" aria-label="Меню"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
|
||||
<div class="hdr-ic"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></div>
|
||||
<div class="hdr-t">@wasrusgen1<span class="hdr-sep"></span><b>КОНСАЛТИНГ</b></div>
|
||||
<div class="hdr-client" id="hdrClient"></div>
|
||||
<div class="hdr-r"><div class="elena-chip"><div class="elena-av">Е</div><div class="elena-nm">Елена</div><div class="elena-dot"></div></div></div>
|
||||
<div class="hdr-r"><button onclick="openIdeas()" title="Предложить идею" style="display:inline-flex;align-items:center;gap:6px;background:rgba(16,185,129,.16);color:#34D399;border:1px solid rgba(16,185,129,.32);border-radius:9px;padding:6px 11px;font-size:12px;font-weight:700;font-family:Inter;cursor:pointer"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg><span class="idea-label">Предложить идею</span></button><div class="elena-chip"><div class="elena-av">Е</div><div class="elena-nm">Елена</div><div class="elena-dot"></div></div></div>
|
||||
</header>
|
||||
<div class="pay-banner" id="payBanner"><span>💳 К оплате: <b id="payAmount"></b></span><button class="pay-btn" onclick="showPayModal()">Оплатить →</button></div>
|
||||
<div id="testBanner" style="display:flex;align-items:center;gap:10px;padding:9px 16px;background:#ECFDF5;border-bottom:1px solid #A7F3D0;color:#065F46;font-size:12.5px;font-weight:500;line-height:1.45">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0"><path d="M9 2v6.5L4.2 17a2 2 0 0 0 1.8 3h12a2 2 0 0 0 1.8-3L15 8.5V2"/><path d="M8 2h8"/><path d="M7.5 13h9"/></svg>
|
||||
<span><b>Тестовая версия.</b> Сервис в режиме обкатки — данные могут обновляться, это нормально. Спасибо, что тестируете вместе с нами!</span>
|
||||
<button onclick="var b=document.getElementById('testBanner');b.style.display='none';try{localStorage.setItem('cab_test_hidden','1')}catch(e){}" aria-label="Скрыть" style="margin-left:auto;flex-shrink:0;background:none;border:none;color:#047857;font-size:16px;line-height:1;cursor:pointer;padding:2px 4px">✕</button>
|
||||
</div>
|
||||
<div class="pay-banner" id="payBanner"><span style="display:inline-flex;align-items:center;gap:7px"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>К оплате: <b id="payAmount"></b></span><button class="pay-btn" onclick="showPayModal()">Оплатить →</button></div>
|
||||
<div class="modal-bg" id="signModal">
|
||||
<div class="modal" style="max-width:520px">
|
||||
<div class="modal-h">Подписание договора</div>
|
||||
<div class="modal-sub">Для продолжения подпишите документы простой электронной подписью</div>
|
||||
<div style="background:var(--subtle);border:1.5px solid var(--border);border-radius:11px;padding:14px;margin-bottom:16px;font-size:13px;line-height:1.6">
|
||||
Ознакомьтесь с документами:<br>
|
||||
📄 <a href="#" onclick="viewDoc('offer');return false" style="color:var(--primary);font-weight:600">Договор-оферта на консультационные услуги</a><br>
|
||||
📄 <a href="#" onclick="viewDoc('pep');return false" style="color:var(--primary);font-weight:600">Соглашение об использовании ПЭП</a><br>
|
||||
📄 <a href="#" onclick="viewDoc('pdn');return false" style="color:var(--primary);font-weight:600">Политика обработки персональных данных</a>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><a href="#" onclick="viewDoc('offer');return false" style="color:var(--primary);font-weight:600">Договор-оферта на консультационные услуги</a><br>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><a href="#" onclick="viewDoc('pep');return false" style="color:var(--primary);font-weight:600">Соглашение об использовании ПЭП</a><br>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><a href="#" onclick="viewDoc('pdn');return false" style="color:var(--primary);font-weight:600">Политика обработки персональных данных</a>
|
||||
</div>
|
||||
<label style="display:flex;align-items:flex-start;gap:10px;margin-bottom:16px;cursor:pointer">
|
||||
<input type="checkbox" id="signAgree" style="width:18px;height:18px;margin-top:2px;flex-shrink:0">
|
||||
@ -203,7 +257,7 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
<div id="signStep2" style="display:none">
|
||||
<div id="signCodeHint" style="font-size:12px;color:var(--primary);background:var(--light);border-radius:8px;padding:8px 12px;margin-bottom:12px"></div>
|
||||
<input id="signCode" placeholder="Код из SMS/email" maxlength="4" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:11px 14px;font-size:18px;font-family:Inter;outline:none;margin-bottom:12px;text-align:center;letter-spacing:8px">
|
||||
<button class="btn btn-p" style="width:100%;justify-content:center" onclick="signConfirm()">✓ Подписать договор</button>
|
||||
<button class="btn btn-p" style="width:100%;justify-content:center;gap:7px" onclick="signConfirm()"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>Подписать договор</button>
|
||||
</div>
|
||||
<button class="modal-close" onclick="document.getElementById('signModal').classList.remove('show')">Отмена</button>
|
||||
</div>
|
||||
@ -219,23 +273,25 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
<div class="modal">
|
||||
<div class="modal-h">Оплата консалтинга</div>
|
||||
<div class="modal-sub">Сумма: <b id="modalAmount"></b> · выберите способ</div>
|
||||
<div class="pay-opt" onclick="payVia('card')"><div class="pay-opt-ic">💳</div><div><div class="pay-opt-t">Банковская карта</div><div class="pay-opt-d">Visa, Mastercard, Мир · мгновенно</div></div></div>
|
||||
<div class="pay-opt" onclick="payVia('sbp')"><div class="pay-opt-ic">📱</div><div><div class="pay-opt-t">СБП — по QR-коду</div><div class="pay-opt-d">Перевод через приложение банка</div></div></div>
|
||||
<div class="pay-opt" onclick="payVia('cash')"><div class="pay-opt-ic">💵</div><div><div class="pay-opt-t">Наличными</div><div class="pay-opt-d">При встрече с консультантом</div></div></div>
|
||||
<div class="pay-opt" onclick="payVia('card')"><div class="pay-opt-ic" style="color:#047857"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg></div><div><div class="pay-opt-t">Банковская карта</div><div class="pay-opt-d">Visa, Mastercard, Мир · мгновенно</div></div></div>
|
||||
<div class="pay-opt" onclick="payVia('sbp')"><div class="pay-opt-ic" style="color:#047857"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg></div><div><div class="pay-opt-t">СБП — по QR-коду</div><div class="pay-opt-d">Перевод через приложение банка</div></div></div>
|
||||
<div class="pay-opt" onclick="payVia('cash')"><div class="pay-opt-ic" style="color:#047857"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12h.01M18 12h.01"/></svg></div><div><div class="pay-opt-t">Наличными</div><div class="pay-opt-d">При встрече с консультантом</div></div></div>
|
||||
<button class="modal-close" onclick="document.getElementById('payModal').classList.remove('show')">Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout">
|
||||
<aside class="sb">
|
||||
<div class="sb-backdrop" id="sbBackdrop" onclick="toggleSb()"></div>
|
||||
<aside class="sb" id="sbNav">
|
||||
<div class="sb-nav">
|
||||
<div class="si" id="si0" onclick="go(0)" style="margin-bottom:4px"><div class="si-num" style="background:rgba(255,255,255,.1);color:rgba(255,255,255,.7)">👤</div><div class="si-body"><div class="si-name">Профиль компании</div><div class="si-sub">О вашей деятельности</div></div></div>
|
||||
<div class="si" id="si0" onclick="go(0)" style="margin-bottom:4px"><div class="si-num" style="background:rgba(255,255,255,.1);color:rgba(255,255,255,.7)"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div><div class="si-body"><div class="si-name">Профиль компании</div><div class="si-sub">О вашей деятельности</div></div></div>
|
||||
<div style="height:1px;background:rgba(255,255,255,.06);margin:4px 18px 8px"></div>
|
||||
<div class="sb-cap">Путь к результату</div>
|
||||
<div class="si active" id="si1" onclick="go(1)"><div class="si-num">1</div><div class="si-body"><div class="si-lbl">Этап 1</div><div class="si-name">Знакомство</div><div class="si-sub">Интервью с Еленой</div></div></div>
|
||||
<div class="si" id="si2" onclick="go(2)"><div class="si-num">2</div><div class="si-body"><div class="si-lbl">Этап 2</div><div class="si-name">Диагностика</div><div class="si-sub">Углубление</div></div></div>
|
||||
<div class="si" id="si3" onclick="go(3)"><div class="si-num">3</div><div class="si-body"><div class="si-lbl">Этап 3</div><div class="si-name">Документы</div><div class="si-sub">Материалы</div></div></div>
|
||||
<div class="si" id="si4" onclick="go(4)"><div class="si-num">4</div><div class="si-body"><div class="si-lbl">Этап 4</div><div class="si-name">Анализ</div><div class="si-sub">Модель бизнеса</div></div></div>
|
||||
<div class="si" id="si5" onclick="go(5)"><div class="si-num">5</div><div class="si-body"><div class="si-lbl">Этап 5</div><div class="si-name">План</div><div class="si-sub">ТЗ на программу</div></div></div>
|
||||
<div class="si active" id="si1" onclick="go(1)"><div class="si-num">1</div><div class="si-body"><div class="si-lbl">Этап 1</div><div class="si-name">Разговор с Еленой</div><div class="si-sub">Знакомство и диагностика</div></div></div>
|
||||
<div class="si" id="si3" onclick="go(3)"><div class="si-num">2</div><div class="si-body"><div class="si-lbl">Этап 2</div><div class="si-name">Документы</div><div class="si-sub">Материалы</div></div></div>
|
||||
<div class="si" id="si4" onclick="go(4)"><div class="si-num">3</div><div class="si-body"><div class="si-lbl">Этап 3</div><div class="si-name">Анализ</div><div class="si-sub">Модель бизнеса</div></div></div>
|
||||
<div class="si" id="si5" onclick="go(5)"><div class="si-num">4</div><div class="si-body"><div class="si-lbl">Этап 4</div><div class="si-name">План</div><div class="si-sub">ТЗ на программу</div></div></div>
|
||||
<div style="height:1px;background:rgba(255,255,255,.06);margin:8px 18px"></div>
|
||||
<div class="si" id="si6" onclick="go(6)"><div class="si-num" style="background:rgba(4,120,87,.25);color:#10B981"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div class="si-body"><div class="si-name">Консультант</div><div class="si-sub">Связаться с Русланом</div></div></div>
|
||||
</div>
|
||||
<div class="sb-foot"><div class="pb-box"><div class="pb-row"><span class="pb-l">Прогресс</span><span class="pb-p" id="pbPct">20%</span></div><div class="pb-track"><div class="pb-fill" id="pbFill" style="width:20%"></div></div></div></div>
|
||||
</aside>
|
||||
@ -243,20 +299,25 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
|
||||
<!-- Профиль компании -->
|
||||
<div class="sv" id="sv0">
|
||||
<div class="hero"><div class="hero-ic">👤</div><div><div class="hero-tag">Профиль компании</div><div class="hero-h">Расскажите о вашей деятельности</div><div class="hero-d">Базовая информация — Елена начнёт разговор уже зная контекст</div></div></div>
|
||||
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div><div><div class="hero-tag">Профиль компании</div><div class="hero-h">Расскажите о вашей деятельности</div><div class="hero-d">Базовая информация — Елена начнёт разговор уже зная контекст</div></div></div>
|
||||
<div class="scroll"><div class="pad">
|
||||
<div style="max-width:600px">
|
||||
<div style="margin-bottom:18px"><label class="pf-l">Ваше имя или название компании</label><input class="pf-i" id="pfName" placeholder="Например: Игорь / ООО «Автодом»"></div>
|
||||
<div style="margin-bottom:18px"><label class="pf-l">Сфера деятельности</label><input class="pf-i" id="pfNiche" placeholder="Например: автосервис, нутрициология, швейное производство"></div>
|
||||
<div style="margin-bottom:22px"><label class="pf-l">Описание деятельности</label><textarea class="pf-t" id="pfDesc" placeholder="Чем занимаетесь, сколько человек, как сейчас всё устроено, что беспокоит. Чем подробнее — тем точнее анализ."></textarea></div>
|
||||
<div style="margin-bottom:22px"><label class="pf-l" style="display:flex;align-items:center;justify-content:space-between">Описание деятельности <button type="button" id="pfMic" class="mic" onclick="toggleMic('pfDesc','pfMic')" title="Надиктовать" style="width:30px;height:30px;border-radius:9px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></button></label><textarea class="pf-t" id="pfDesc" placeholder="Чем занимаетесь, сколько человек, как сейчас всё устроено, что беспокоит. Можно надиктовать голосом"></textarea></div>
|
||||
<button class="btn btn-p" id="pfSave" onclick="saveProfile()" style="padding:12px 24px;font-size:14px">Сохранить и начать с Еленой →</button>
|
||||
</div>
|
||||
</div></div>
|
||||
</div>
|
||||
|
||||
<!-- Этап 1 — Знакомство (живой чат) -->
|
||||
<!-- Этап 1 — Разговор с Еленой (знакомство + диагностика) -->
|
||||
<div class="sv active" id="sv1">
|
||||
<div class="hero"><div class="hero-ic">💬</div><div><div class="hero-tag">Этап 1 из 5 · В процессе</div><div class="hero-h">Знакомство</div><div class="hero-d">Расскажите Елене о вашем бизнесе — текстом или голосом</div></div></div>
|
||||
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div><div class="hero-tag">Этап 1 из 4 · В процессе</div><div class="hero-h">Разговор с Еленой</div><div class="hero-d">Расскажите о бизнесе — Елена знакомится и сразу углубляется в детали. Текстом или голосом.</div></div></div>
|
||||
<div id="startHint" style="margin:12px 16px 0;padding:11px 14px;background:#F0FDF4;border:1px solid #BBF7D0;border-radius:11px;font-size:13px;color:#065F46;line-height:1.5;display:flex;gap:9px;align-items:flex-start">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;margin-top:1px"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg>
|
||||
<div><b>С чего начать:</b> расскажите Елене о своём бизнесе — чем занимаетесь, сколько человек в команде, что сейчас болит. Можно <b>голосом</b> (кнопка микрофона) или текстом. Хватит 10–15 минут.</div>
|
||||
<button onclick="var h=document.getElementById('startHint');h.style.display='none';try{localStorage.setItem('cab_hint_hidden','1')}catch(e){}" aria-label="Скрыть" style="flex-shrink:0;background:none;border:none;color:#047857;font-size:15px;line-height:1;cursor:pointer;padding:2px 4px">✕</button>
|
||||
</div>
|
||||
<div class="scroll"><div class="chat" id="chat"></div></div>
|
||||
<div class="inbar">
|
||||
<textarea class="inp" id="inp" rows="1" placeholder="Напишите или скажите Елене..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
|
||||
@ -265,18 +326,12 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Этап 2 — Диагностика -->
|
||||
<div class="sv" id="sv2">
|
||||
<div class="hero"><div class="hero-ic">🔍</div><div><div class="hero-tag">Этап 2 из 5</div><div class="hero-h">Диагностика</div><div class="hero-d">Елена уточняет детали — продолжайте разговор</div></div></div>
|
||||
<div class="scroll"><div class="pad"><div class="run-card"><div class="run-ic">🔍</div><div class="run-t">Диагностика идёт в чате</div><div class="run-d">Елена задаёт уточняющие вопросы прямо в интервью (Этап 1). Когда данных достаточно — переходите к Анализу.</div><button class="btn btn-p" onclick="go(1)">← Вернуться к интервью</button></div></div></div>
|
||||
</div>
|
||||
|
||||
<!-- Этап 3 — Документы -->
|
||||
<div class="sv" id="sv3">
|
||||
<div class="hero"><div class="hero-ic">📁</div><div><div class="hero-tag">Этап 3 из 5</div><div class="hero-h">Документы</div><div class="hero-d">Загрузите материалы — Елена учтёт их в анализе</div></div></div>
|
||||
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></div><div><div class="hero-tag">Этап 2 из 4</div><div class="hero-h">Документы</div><div class="hero-d">Загрузите материалы — Елена учтёт их в анализе</div></div></div>
|
||||
<div class="scroll"><div class="pad">
|
||||
<div class="drop" id="dropZone" onclick="document.getElementById('fileInp').click()">
|
||||
<div class="drop-ic">📎</div>
|
||||
<div class="drop-ic" style="color:#94A3B8"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></div>
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:4px">Нажмите или перетащите документы</div>
|
||||
<div style="font-size:12px;color:#9ca3af">PDF, Word, Excel, txt · прайс, оргструктура, отчёты</div>
|
||||
<input type="file" id="fileInp" multiple style="display:none" onchange="handleFiles(this.files)">
|
||||
@ -287,16 +342,42 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
|
||||
<!-- Этап 4 — Анализ -->
|
||||
<div class="sv" id="sv4">
|
||||
<div class="hero"><div class="hero-ic">🧠</div><div><div class="hero-tag">Этап 4 из 5</div><div class="hero-h">Анализ бизнеса</div><div class="hero-d">Елена строит модель вашего бизнеса</div></div></div>
|
||||
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></div><div><div class="hero-tag">Этап 3 из 4</div><div class="hero-h">Анализ бизнеса</div><div class="hero-d">Елена строит модель вашего бизнеса</div></div></div>
|
||||
<div class="scroll"><div class="pad" id="anPad"></div></div>
|
||||
</div>
|
||||
|
||||
<!-- Этап 5 — План -->
|
||||
<div class="sv" id="sv5">
|
||||
<div class="hero"><div class="hero-ic">🚀</div><div><div class="hero-tag">Этап 5 из 5</div><div class="hero-h">План — ТЗ на программу</div><div class="hero-d">Проект системы для вашего бизнеса</div></div></div>
|
||||
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg></div><div><div class="hero-tag">Этап 4 из 4</div><div class="hero-h">План — ТЗ на программу</div><div class="hero-d">Проект системы для вашего бизнеса</div></div></div>
|
||||
<div class="scroll"><div class="pad" id="specPad"></div></div>
|
||||
</div>
|
||||
|
||||
<!-- Канал «Консультант» (живой) -->
|
||||
<div class="sv" id="sv6">
|
||||
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div style="flex:1"><div class="hero-tag">Личный консультант</div><div class="hero-h">Связаться с Русланом</div><div class="hero-d">Вопросы по проекту, срокам, оплате — напишите напрямую</div></div></div>
|
||||
<div class="scroll"><div class="chat" id="opChat"></div></div>
|
||||
<div class="inbar">
|
||||
<textarea class="inp" id="opInp" rows="1" placeholder="Напишите консультанту…" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();opSend()}"></textarea>
|
||||
<button class="icon-btn mic" id="opMic" title="Голосом" onclick="toggleMic('opInp','opMic')"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></button>
|
||||
<button class="icon-btn send" id="opSendBtn" onclick="opSend()"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Спросить Елену — док на этапах 3-5 -->
|
||||
<div class="askdock" id="askDock">
|
||||
<div class="askdock-head" onclick="toggleAsk()"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" style="margin-right:6px;vertical-align:-3px"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>Спросить Елену <span class="ad-sub" id="adSub">об этом этапе</span><svg class="ad-chev" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></div>
|
||||
<div class="askdock-body">
|
||||
<div class="askdock-thread" id="askThread"></div>
|
||||
<div class="askdock-inbar">
|
||||
<textarea id="askInp" rows="1" placeholder="Спросите Елену об этом этапе…" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();askElena()}"></textarea>
|
||||
<input type="file" id="askFile" multiple style="display:none" onchange="askAttach(this.files)">
|
||||
<button class="icon-btn" id="askAttachBtn" title="Приложить документ" onclick="document.getElementById('askFile').click()" style="background:var(--light);border:1.5px solid var(--border);color:var(--primary)"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></button>
|
||||
<button class="icon-btn mic" id="askMic" title="Голосом" onclick="toggleMic('askInp','askMic')"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></button>
|
||||
<button class="icon-btn send" id="askSend" onclick="askElena()"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -307,21 +388,76 @@ const urlToken=new URLSearchParams(location.search).get("t");
|
||||
if(urlToken)localStorage.setItem("cab_token",urlToken);
|
||||
let token=urlToken||localStorage.getItem("cab_token"), state=null, cur=1;
|
||||
const chat=document.getElementById("chat"), inp=document.getElementById("inp");
|
||||
const PCTS={1:20,2:40,3:60,4:80,5:100};
|
||||
const PCTS={1:25,3:50,4:75,5:100}; // 4 шага для клиента (Этап 1 объединён)
|
||||
function esc(s){return (s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}
|
||||
function fmt(s){return esc(s).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>")}
|
||||
/* ── Иконки по брендбуку: Lucide, stroke 1.75, fill none, round ── */
|
||||
const ICONS={
|
||||
user:'<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
|
||||
message:'<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
||||
folder:'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>',
|
||||
file:'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>',
|
||||
chart:'<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
|
||||
activity:'<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>',
|
||||
process:'<circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 19.07a10 10 0 0 1 0-14.14"/>',
|
||||
team:'<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
||||
clipboard:'<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>',
|
||||
idea:'<path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/>',
|
||||
paperclip:'<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/>',
|
||||
card:'<rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/>',
|
||||
phone:'<rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>',
|
||||
cash:'<rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12h.01M18 12h.01"/>',
|
||||
printer:'<polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>',
|
||||
download:'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>',
|
||||
lock:'<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
||||
calendar:'<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>',
|
||||
check:'<polyline points="20 6 9 17 4 12"/>',
|
||||
alert:'<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
|
||||
mic:'<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>',
|
||||
receipt:'<path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1-2-1z"/><path d="M16 8H8M16 12H8M13 16H8"/>',
|
||||
search:'<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>',
|
||||
target:'<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
||||
send:'<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>',
|
||||
close:'<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
|
||||
checkCircle:'<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
|
||||
sparkle:'<path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"/>',
|
||||
gift:'<polyline points="20 12 20 22 4 22 4 12"/><rect x="2" y="7" width="20" height="5"/><line x1="12" y1="22" x2="12" y2="7"/><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/>',
|
||||
dot:'<circle cx="12" cy="12" r="5"/>'
|
||||
};
|
||||
function ic(n,s,sw){s=s||20;return '<svg style="display:inline-block;vertical-align:middle" width="'+s+'" height="'+s+'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="'+(sw||1.75)+'" stroke-linecap="round" stroke-linejoin="round">'+(ICONS[n]||'')+'</svg>';}
|
||||
|
||||
function toggleSb(){
|
||||
const sb=document.getElementById('sbNav'), bd=document.getElementById('sbBackdrop');
|
||||
const open=sb.classList.toggle('open'); bd.classList.toggle('show',open);
|
||||
}
|
||||
function closeSb(){
|
||||
const sb=document.getElementById('sbNav'), bd=document.getElementById('sbBackdrop');
|
||||
if(sb)sb.classList.remove('open'); if(bd)bd.classList.remove('show');
|
||||
}
|
||||
function go(n){
|
||||
if(document.getElementById('si'+n).classList.contains('locked'))return;
|
||||
if(window.innerWidth<=680)closeSb();
|
||||
cur=n;
|
||||
document.querySelectorAll('.sv').forEach(s=>s.classList.remove('active'));
|
||||
document.getElementById('sv'+n).classList.add('active');
|
||||
document.querySelectorAll('.si').forEach(s=>s.classList.remove('active'));
|
||||
document.getElementById('si'+n).classList.add('active');
|
||||
if(PCTS[n]!=null){document.getElementById('pbPct').textContent=PCTS[n]+'%';document.getElementById('pbFill').style.width=PCTS[n]+'%';}
|
||||
if(n===1){renderChat1();requestAnimationFrame(scrollChatBottom);} // ре-рендер из общей беседы
|
||||
if(n===6){renderOpChat();startOpPoll();}else{stopOpPoll();}
|
||||
if(n===3)renderDocs();
|
||||
if(n===4)renderAnalysis();
|
||||
if(n===5)renderSpecPane();
|
||||
// Telegram: нативная «Назад» + лёгкий хаптик
|
||||
const _tg=window.__tg;
|
||||
if(_tg){try{if(_tg.BackButton){if(n&&n!==1)_tg.BackButton.show();else _tg.BackButton.hide();}if(_tg.HapticFeedback)_tg.HapticFeedback.selectionChanged();}catch(e){}}
|
||||
// Док «Спросить Елену» — только на этапах 3-5
|
||||
const dock=document.getElementById("askDock");
|
||||
if(dock){
|
||||
if(n>=3&&n<=5){dock.classList.add("show");dock.classList.add("open");document.getElementById("adSub").textContent=STAGE_LBL[n]||"об этапе";
|
||||
if(!dock.dataset.rendered){renderAskThread();dock.dataset.rendered="1";}}
|
||||
else dock.classList.remove("show");
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProfile(){
|
||||
@ -340,34 +476,134 @@ async function saveProfile(){
|
||||
}catch(e){alert("Ошибка: "+e.message);btn.disabled=false}
|
||||
}
|
||||
|
||||
/* ── Voice ── */
|
||||
let recog=null,recording=false;
|
||||
/* ── Voice (переиспользуемый — любое поле) ── */
|
||||
let recog=null,recording=false,micTarget=null,micBtnEl=null;
|
||||
const SR=window.SpeechRecognition||window.webkitSpeechRecognition;
|
||||
if(SR){recog=new SR();recog.lang="ru-RU";recog.continuous=true;recog.interimResults=true;let base="";
|
||||
recog.onresult=e=>{let fin="",intr="";for(let i=e.resultIndex;i<e.results.length;i++){const t=e.results[i][0].transcript;if(e.results[i].isFinal)fin+=t;else intr+=t}if(fin)base+=fin;inp.value=(base+intr).trim();inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"};
|
||||
recog.onstart=()=>{base=inp.value?inp.value+" ":""};
|
||||
recog.onresult=e=>{let fin="",intr="";for(let i=e.resultIndex;i<e.results.length;i++){const t=e.results[i][0].transcript;if(e.results[i].isFinal)fin+=t;else intr+=t}if(fin)base+=fin;if(micTarget){micTarget.value=(base+intr).trim();if(micTarget.tagName==='TEXTAREA'){micTarget.style.height="auto";micTarget.style.height=Math.min(micTarget.scrollHeight,120)+"px";}}};
|
||||
recog.onstart=()=>{base=(micTarget&&micTarget.value)?micTarget.value+" ":""};
|
||||
recog.onend=()=>{if(recording){try{recog.start()}catch(e){}}};
|
||||
recog.onerror=e=>{if(e.error==="not-allowed"){alert("Разрешите доступ к микрофону");stopMic()}};
|
||||
}
|
||||
function toggleMic(){if(!recog){alert("Голосовой ввод работает в Chrome");return}recording?stopMic():startMic()}
|
||||
function startMic(){recording=true;document.getElementById("micBtn").classList.add("rec");document.getElementById("micHint").classList.add("show");try{recog.start()}catch(e){}}
|
||||
function stopMic(){recording=false;document.getElementById("micBtn").classList.remove("rec");document.getElementById("micHint").classList.remove("show");try{recog.stop()}catch(e){}inp.focus()}
|
||||
function toggleMic(targetId,btnId){
|
||||
if(!recog){alert("Голосовой ввод работает в Chrome / Android. На iPhone — печатайте.");return}
|
||||
const t=document.getElementById(targetId||'inp'), b=document.getElementById(btnId||'micBtn');
|
||||
if(recording&&micTarget===t){stopMic();return;}
|
||||
if(recording)stopMic();
|
||||
micTarget=t;micBtnEl=b;startMic();
|
||||
}
|
||||
function startMic(){recording=true;if(micBtnEl)micBtnEl.classList.add("rec");if(micBtnEl&&micBtnEl.id==='micBtn'){const h=document.getElementById("micHint");if(h)h.classList.add("show");}try{recog.start()}catch(e){}}
|
||||
function stopMic(){recording=false;if(micBtnEl)micBtnEl.classList.remove("rec");const h=document.getElementById("micHint");if(h)h.classList.remove("show");try{recog.stop()}catch(e){}if(micTarget)micTarget.focus();}
|
||||
|
||||
/* ── Chat ── */
|
||||
function addMsg(role,text){const m=document.createElement("div");m.className="msg "+(role==="user"?"user":"");m.innerHTML=`<div class="av ${role==='user'?'u':'e'}">${role==='user'?'Я':'Е'}</div><div class="bb ${role==='user'?'out':'in'}">${fmt(text)}</div>`;chat.appendChild(m);chat.scrollTop=chat.scrollHeight}
|
||||
function showTyping(){const t=document.createElement("div");t.className="msg";t.id="typing";t.innerHTML=`<div class="av e">Е</div><div class="typing"><span></span><span></span><span></span></div>`;chat.appendChild(t);chat.scrollTop=chat.scrollHeight}
|
||||
// Скроллит реальный контейнер (.scroll), а не .chat (у него нет своего скролла). Мгновенно, без анимации.
|
||||
function scrollChatBottom(){const sc=chat.parentElement;if(sc)sc.scrollTop=sc.scrollHeight;}
|
||||
function addMsg(role,text){const m=document.createElement("div");m.className="msg "+(role==="user"?"user":"");m.innerHTML=`<div class="av ${role==='user'?'u':'e'}">${role==='user'?'Я':'Е'}</div><div class="bb ${role==='user'?'out':'in'}">${fmt(text)}</div>`;chat.appendChild(m);scrollChatBottom()}
|
||||
function showTyping(){const t=document.createElement("div");t.className="msg";t.id="typing";t.innerHTML=`<div class="av e">Е</div><div class="typing"><span></span><span></span><span></span></div>`;chat.appendChild(t);scrollChatBottom()}
|
||||
function hideTyping(){const t=document.getElementById("typing");if(t)t.remove()}
|
||||
|
||||
/* ── Канал «Консультант» (живой) ── */
|
||||
function opMsg(role,text){const c=document.getElementById("opChat");if(!c)return;const m=document.createElement("div");m.className="msg "+(role==="user"?"user":"");m.innerHTML=`<div class="av ${role==='user'?'u':'e'}">${role==='user'?'Я':'Р'}</div><div class="bb ${role==='user'?'out':'in'}">${fmt(text)}</div>`;c.appendChild(m);const sc=c.parentElement;if(sc)sc.scrollTop=sc.scrollHeight;}
|
||||
function renderOpChat(){const c=document.getElementById("opChat");if(!c)return;c.innerHTML="";const m=state&&state.operator_chat||[];if(!m.length){c.innerHTML='<div style="text-align:center;color:#cbd5e1;font-size:13px;padding:24px 16px">Напишите консультанту — Руслан ответит здесь и пришлёт уведомление в Telegram.</div>';return;}m.forEach(x=>opMsg(x.role==="user"?"user":"elena",x.content));}
|
||||
async function opSend(){const inp=document.getElementById("opInp");const t=inp.value.trim();if(!t)return;inp.value="";inp.style.height="auto";opMsg("user",t);state.operator_chat=state.operator_chat||[];state.operator_chat.push({role:"user",content:t});
|
||||
try{const r=await fetch(`${API}/api/operator-chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,message:t})});const d=await r.json();if(d.messages)state.operator_chat=d.messages;}catch(e){opMsg("elena","Не доставлено: "+e.message);}
|
||||
}
|
||||
let opPollTimer=null;
|
||||
async function opPollOnce(){try{const r=await fetch(`${API}/api/operator-chat?token=${encodeURIComponent(token)}`);const d=await r.json();if(d.messages&&d.messages.length!==(state.operator_chat||[]).length){state.operator_chat=d.messages;renderOpChat();}}catch(e){}}
|
||||
function startOpPoll(){stopOpPoll();opPollOnce();opPollTimer=setInterval(opPollOnce,15000);}
|
||||
function stopOpPoll(){if(opPollTimer){clearInterval(opPollTimer);opPollTimer=null;}}
|
||||
/* ── Предложения клиента ── */
|
||||
const SUG_ST={new:[ic('dot',12)+' Новое','#EFF6FF','#2563EB'],discussion:[ic('message',12)+' На обсуждении','#FEF3C7','#92400E'],accepted:[ic('checkCircle',12)+' Принято','#D1FAE5','#047857'],rejected:[ic('close',12)+' Отклонено','#FEE2E2','#B91C1C']};
|
||||
function openIdeas(){
|
||||
const sgs=state.suggestions||[];
|
||||
const list=sgs.length?sgs.map(s=>{const st=SUG_ST[s.status]||SUG_ST.new;return `<div style="border:1px solid var(--border);border-radius:10px;padding:10px 12px;margin-bottom:8px"><div style="font-size:13px">${esc(s.text)}</div><div style="margin-top:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="font-size:11px;font-weight:700;color:${st[2]};background:${st[1]};padding:2px 8px;border-radius:6px">${st[0]}</span>${s.decision?`<span style="font-size:11px;color:#6B7280">— ${esc(s.decision)}</span>`:''}</div></div>`}).join(''):'<div style="font-size:13px;color:#9ca3af;text-align:center;padding:10px">Пока нет. Предложите первую идею — мы рассмотрим её.</div>';
|
||||
const m=document.createElement('div');m.id='ideaModal';m.style.cssText='position:fixed;inset:0;background:rgba(15,15,26,.5);z-index:260;display:flex;align-items:center;justify-content:center;padding:18px';m.onclick=()=>m.remove();
|
||||
m.innerHTML=`<div onclick="event.stopPropagation()" style="background:#fff;border-radius:16px;padding:22px;width:100%;max-width:420px;max-height:84vh;display:flex;flex-direction:column">
|
||||
<div style="font-size:18px;font-weight:800;font-family:Montserrat,Inter;margin-bottom:4px;display:flex;align-items:center;gap:8px">${ic('idea',20)} Мои предложения</div>
|
||||
<div style="font-size:12px;color:#6B7280;margin-bottom:12px">Ваши идеи по проекту. Консультант рассмотрит каждую и ответит решением.</div>
|
||||
<div style="overflow-y:auto;flex:1;margin-bottom:12px">${list}</div>
|
||||
<textarea id="ideaInp" rows="2" placeholder="Опишите идею или улучшение…" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:10px 12px;font-size:13px;font-family:Inter;resize:vertical;outline:none;box-sizing:border-box"></textarea>
|
||||
<div style="display:flex;gap:8px;margin-top:10px"><button onclick="document.getElementById('ideaModal').remove()" style="flex:1;padding:11px;background:#F1F5F9;color:#475569;border:none;border-radius:10px;font-weight:700;font-family:Inter;cursor:pointer">Закрыть</button><button id="ideaOk" onclick="submitIdea()" style="flex:1.4;padding:11px;background:#047857;color:#fff;border:none;border-radius:10px;font-weight:700;font-family:Inter;cursor:pointer">Предложить →</button></div>
|
||||
</div>`;
|
||||
document.body.appendChild(m);setTimeout(()=>{const e=document.getElementById('ideaInp');if(e)e.focus();},40);
|
||||
}
|
||||
async function submitIdea(){
|
||||
const t=(document.getElementById('ideaInp').value||'').trim();if(!t)return;
|
||||
const b=document.getElementById('ideaOk');b.disabled=true;b.textContent='Отправляю…';
|
||||
try{const r=await fetch(`${API}/api/suggestion`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,text:t})});const d=await r.json();
|
||||
if(d.suggestions)state.suggestions=d.suggestions;
|
||||
const mm=document.getElementById('ideaModal');if(mm)mm.remove();openIdeas();
|
||||
}catch(e){b.disabled=false;b.textContent='Предложить →';}
|
||||
}
|
||||
/* ── Спросить Елену (этапы 3-5) ── */
|
||||
const STAGE_LBL={3:"о документах",4:"о стратегии и модели",5:"о ТЗ и плане"};
|
||||
function toggleAsk(){document.getElementById("askDock").classList.toggle("open")}
|
||||
function addAsk(role,text,dev){
|
||||
const t=document.getElementById("askThread");if(!t)return;
|
||||
const m=document.createElement("div");m.className="am "+(role==="user"?"u":"e");
|
||||
m.innerHTML=`<div class="am-av">${role==="user"?"Я":"Е"}</div><div class="am-bb">${fmt(text)}${dev?'<div class="am-dev">'+ic('alert',12)+' Ваше пожелание зафиксировано — учтём при внедрении</div>':''}</div>`;
|
||||
t.appendChild(m);t.scrollTop=t.scrollHeight;
|
||||
}
|
||||
function renderAskThread(){
|
||||
const t=document.getElementById("askThread");if(!t)return;
|
||||
t.innerHTML="";(state&&state.qa||[]).forEach(m=>addAsk(m.role==="user"?"user":"elena",m.content));
|
||||
}
|
||||
async function askAttach(files){
|
||||
document.getElementById("askDock").classList.add("open");
|
||||
for(const f of files){
|
||||
addAsk("user","Файл: "+f.name);
|
||||
try{
|
||||
const b64=await new Promise((res,rej)=>{const r=new FileReader();r.onload=()=>res(r.result);r.onerror=rej;r.readAsDataURL(f)});
|
||||
const r=await fetch(`${API}/api/upload`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,filename:f.name,content:b64})});
|
||||
const d=await r.json();
|
||||
if(d.error){addAsk("elena","Не удалось загрузить «"+f.name+"»: "+d.error);continue;}
|
||||
state.documents=state.documents||[];state.documents.push({filename:d.filename,size:d.size});
|
||||
addAsk("elena","Документ «"+f.name+"» приложен — учту его в ответах по проекту. Спрашивайте.");
|
||||
}catch(e){addAsk("elena","Ошибка загрузки: "+e.message);}
|
||||
}
|
||||
document.getElementById("askFile").value="";
|
||||
}
|
||||
async function askElena(){
|
||||
const inp=document.getElementById("askInp");const text=inp.value.trim();if(!text)return;
|
||||
inp.value="";inp.style.height="auto";
|
||||
document.getElementById("askDock").classList.add("open");
|
||||
addAsk("user",text);
|
||||
const btn=document.getElementById("askSend");btn.disabled=true;
|
||||
const tp=document.createElement("div");tp.className="am e";tp.id="askTyping";tp.innerHTML='<div class="am-av">Е</div><div class="am-bb">…</div>';
|
||||
document.getElementById("askThread").appendChild(tp);
|
||||
try{
|
||||
const r=await fetch(`${API}/api/ask`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,message:text,stage:String(cur)})});
|
||||
const d=await r.json();
|
||||
const x=document.getElementById("askTyping");if(x)x.remove();
|
||||
addAsk("elena",d.reply||("Ошибка: "+(d.error||"?")),d.deviation_recorded);
|
||||
state.qa=state.qa||[];state.qa.push({role:"user",content:text},{role:"assistant",content:d.reply||""});
|
||||
}catch(e){const x=document.getElementById("askTyping");if(x)x.remove();addAsk("elena","Ошибка связи: "+e.message)}
|
||||
btn.disabled=false;
|
||||
}
|
||||
|
||||
async function init(){
|
||||
// Telegram Mini App: развернуть на весь экран + токен из start_param если нет в URL
|
||||
// ── Telegram Mini App: полноценная интеграция ──
|
||||
const tg=window.Telegram&&window.Telegram.WebApp;
|
||||
if(tg){try{tg.ready();tg.expand();const sp=tg.initDataUnsafe&&tg.initDataUnsafe.start_param;if(sp&&!token){token=sp;localStorage.setItem("cab_token",token);}}catch(e){}}
|
||||
if(tg){try{
|
||||
tg.ready();tg.expand();
|
||||
const sp=tg.initDataUnsafe&&tg.initDataUnsafe.start_param;if(sp&&!token){token=sp;localStorage.setItem("cab_token",token);}
|
||||
// Тема под бренд: тёмная шапка, светлый фон
|
||||
if(tg.setHeaderColor)tg.setHeaderColor('#0F0F1A');
|
||||
if(tg.setBackgroundColor)tg.setBackgroundColor('#F5F6F8');
|
||||
// Защита от случайного смахивания вниз (не терять диалог)
|
||||
if(tg.enableClosingConfirmation)tg.enableClosingConfirmation();
|
||||
// Нативная кнопка «Назад» Telegram → к разговору с Еленой
|
||||
if(tg.BackButton){tg.BackButton.onClick(()=>go(1));}
|
||||
window.__tg=tg;
|
||||
}catch(e){}}
|
||||
if(token){const r=await fetch(`${API}/api/project/${token}`);if(r.ok){state=await r.json();renderAll();
|
||||
fillProfile();
|
||||
// Если профиль не заполнен — открыть вкладку Профиль
|
||||
if(!state.description && !state.client_name) go(0); else go(1);
|
||||
return;}}
|
||||
const r=await fetch(`${API}/api/project/new`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})});
|
||||
const _src=new URLSearchParams(location.search).get('src');
|
||||
const _body=_src?{source:_src==='landing'?'Лендинг':_src}:{};
|
||||
const r=await fetch(`${API}/api/project/new`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_body)});
|
||||
const d=await r.json();token=d.token;localStorage.setItem("cab_token",token);
|
||||
state={messages:[],client_name:"",niche:"",description:""};
|
||||
go(0); // новый клиент → сразу профиль
|
||||
@ -382,7 +618,7 @@ function fillProfile(){
|
||||
function renderDocs(){
|
||||
const dl=document.getElementById("docList");if(!dl)return;
|
||||
const docs=state.documents||[];
|
||||
dl.innerHTML=docs.length?docs.map(d=>`<div style="display:flex;align-items:center;gap:10px;background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px 14px;margin-bottom:8px"><span style="font-size:18px">📄</span><div style="flex:1"><div style="font-size:13px;font-weight:600">${esc(d.filename)}</div><div style="font-size:11px;color:#9ca3af">${(d.size/1024).toFixed(0)} КБ · учтён в анализе</div></div><span style="color:var(--primary);font-weight:700;font-size:13px">✓</span></div>`).join(""):'<div style="text-align:center;color:#cbd5e1;font-size:13px;padding:10px">Документов пока нет</div>';
|
||||
dl.innerHTML=docs.length?docs.map(d=>{const u=`${API}/api/doc?token=${encodeURIComponent(token)}&name=${encodeURIComponent(d.filename)}`;return `<div style="display:flex;align-items:center;gap:10px;background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px 14px;margin-bottom:8px"><span style="color:#047857;display:inline-flex">${ic('file',18)}</span><div style="flex:1;min-width:0"><a href="${u}" target="_blank" rel="noopener" style="font-size:13px;font-weight:600;color:var(--primary);text-decoration:none">${esc(d.filename)}</a><div style="font-size:11px;color:#9ca3af">${(d.size/1024).toFixed(0)} КБ · учтён в анализе</div></div><a href="${u}&dl=1" title="Скачать" style="color:#9ca3af;text-decoration:none;display:inline-flex">${ic('download',16)}</a></div>`}).join(""):'<div style="text-align:center;color:#cbd5e1;font-size:13px;padding:10px">Документов пока нет</div>';
|
||||
}
|
||||
async function handleFiles(files){
|
||||
const dl=document.getElementById("docList");
|
||||
@ -392,19 +628,19 @@ async function handleFiles(files){
|
||||
const b64=await new Promise((res,rej)=>{const r=new FileReader();r.onload=()=>res(r.result);r.onerror=rej;r.readAsDataURL(f)});
|
||||
const r=await fetch(`${API}/api/upload`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,filename:f.name,content:b64})});
|
||||
const d=await r.json();
|
||||
if(d.error){tmp.innerHTML=`❌ ${esc(f.name)}: ${d.error}`;continue}
|
||||
if(d.error){tmp.innerHTML=`${ic('alert',13)} ${esc(f.name)}: ${d.error}`;continue}
|
||||
state.documents=state.documents||[];state.documents.push({filename:d.filename,size:d.size});
|
||||
}catch(e){tmp.innerHTML=`❌ ${esc(f.name)}: ${e.message}`;continue}
|
||||
}catch(e){tmp.innerHTML=`${ic('alert',13)} ${esc(f.name)}: ${e.message}`;continue}
|
||||
}
|
||||
renderDocs();
|
||||
}
|
||||
function renderAll(){
|
||||
if(state.client_name)document.getElementById("hdrClient").textContent="· "+state.client_name;
|
||||
chat.innerHTML="";
|
||||
state.messages.forEach(m=>addMsg(m.role==="user"?"user":"elena",m.content));
|
||||
renderChat1();
|
||||
unlockStages();
|
||||
checkPayment();
|
||||
}
|
||||
function renderChat1(){const c=document.getElementById("chat");if(!c)return;c.innerHTML="";(state.messages||[]).forEach(m=>addMsg(m.role==="user"?"user":"elena",m.content));}
|
||||
function money(n){return (n||0).toLocaleString("ru-RU")+" ₽"}
|
||||
function checkPayment(){
|
||||
const crm=state.crm||{};const deal=crm.deal_amount||0;
|
||||
@ -451,7 +687,7 @@ async function signConfirm(){
|
||||
state.signed=true;
|
||||
document.getElementById("signModal").classList.remove("show");
|
||||
document.getElementById("signStep1").style.display="block";document.getElementById("signStep2").style.display="none";
|
||||
alert("✓ Договор подписан\nПодписант: "+d.identifier);
|
||||
alert("Договор подписан.\nПодписант: "+d.identifier);
|
||||
document.getElementById("payModal").classList.add("show"); // сразу к оплате
|
||||
}catch(e){alert("Ошибка: "+e.message)}
|
||||
}
|
||||
@ -462,7 +698,7 @@ async function payVia(method){
|
||||
const r=await fetch(`${API}/api/payment/create`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,amount,method,return_url:location.href})});
|
||||
const d=await r.json();
|
||||
if(d.error){alert("Ошибка: "+d.error);return}
|
||||
if(method==="cash"){alert("💵 "+d.instructions);return}
|
||||
if(method==="cash"){alert(d.instructions);return}
|
||||
if(d.demo){alert("ДЕМО-режим: ЮKassa ещё не подключена.\n\n"+d.note);return}
|
||||
if(d.qr){alert("СБП: отсканируйте QR в приложении банка\n\n"+(d.qr||""));return}
|
||||
if(d.confirmation_url){location.href=d.confirmation_url;return}
|
||||
@ -471,7 +707,7 @@ async function payVia(method){
|
||||
function unlockStages(){
|
||||
// Этап 4 доступен если есть достаточно сообщений
|
||||
const enough=state.messages.length>=3;
|
||||
[2,3,4,5].forEach(n=>{const si=document.getElementById('si'+n);if(enough||n<=3)si.classList.remove('locked');});
|
||||
[3,4,5].forEach(n=>{const si=document.getElementById('si'+n);if(si&&(enough||n===3))si.classList.remove('locked');});
|
||||
}
|
||||
|
||||
async function sendMsg(){
|
||||
@ -490,42 +726,131 @@ async function sendMsg(){
|
||||
let anTab="canvas";
|
||||
function renderAnalysis(){
|
||||
const pad=document.getElementById("anPad");
|
||||
pad.innerHTML=`<div class="an-tabs"><div class="an-tab ${anTab==='canvas'?'active':''}" onclick="setAnTab('canvas')">📊 Стратегия</div><div class="an-tab ${anTab==='idef0'?'active':''}" onclick="setAnTab('idef0')">🔧 Функции</div></div><div id="anContent"></div>`;
|
||||
pad.innerHTML=`<div class="an-tabs"><div class="an-tab ${anTab==='canvas'?'active':''}" onclick="setAnTab('canvas')">${ic('chart',16)} Стратегия</div><div class="an-tab ${anTab==='idef0'?'active':''}" onclick="setAnTab('idef0')">${ic('process',16)} Функции</div><div class="an-tab ${anTab==='org'?'active':''}" onclick="setAnTab('org')">${ic('team',16)} Организация</div></div><div id="anContent"></div>`;
|
||||
renderAnContent();
|
||||
}
|
||||
function setAnTab(t){anTab=t;renderAnalysis()}
|
||||
function renderAnContent(){
|
||||
const c=document.getElementById("anContent");
|
||||
if(anTab==='canvas'){
|
||||
if(!state.canvas){c.innerHTML=runCard("canvas","📊","Стратегическая модель","Елена построит Business Model Canvas — как устроен ваш бизнес и как он зарабатывает.","Построить стратегию →");return}
|
||||
if(!state.canvas){c.innerHTML=runCard("canvas",ic('chart',30),"Стратегическая модель","Елена построит Business Model Canvas — как устроен ваш бизнес и как он зарабатывает.","Построить стратегию →");return}
|
||||
c.innerHTML=renderCanvas(state.canvas);
|
||||
}else{
|
||||
if(!state.model){c.innerHTML=runCard("model","🔧","Функциональная модель","Елена разложит бизнес на функции (IDEF0): входы, выходы, нормы, ресурсы и разрывы.","Построить модель →");return}
|
||||
}else if(anTab==='idef0'){
|
||||
if(!state.model){c.innerHTML=runCard("model",ic('process',30),"Функциональная модель","Елена разложит бизнес на функции (IDEF0): входы, выходы, нормы, ресурсы и разрывы.","Построить модель →");return}
|
||||
c.innerHTML=renderIdef(state.model);
|
||||
}else{
|
||||
c.innerHTML=renderOrg();
|
||||
}
|
||||
}
|
||||
function renderOrg(){
|
||||
if(!state.model)return runCard(null,ic('alert',30),"Сначала функции","Оргструктура строится из функциональной модели. Постройте модель на вкладке «Функции».","→ К функциям",()=>setAnTab('idef0'));
|
||||
let h='';
|
||||
if(!state.orgchart)h+=runCard("orgchart",ic('team',30),"Целевая оргструктура","Елена построит оргструктуру из модели: кто за что отвечает, подчинённость, штат, узкие места.","Построить оргструктуру →");
|
||||
else h+=renderOrgChart(state.orgchart);
|
||||
if(state.orgchart){
|
||||
if(!state.jobs)h+=`<div style="height:14px"></div>`+runCard("jobs",ic('clipboard',30),"Должностные инструкции","По ролям: зоны ответственности, KPI, полномочия. С учётом ваших пожеланий (отклонений).","Собрать инструкции →");
|
||||
else h+=`<div style="height:14px"></div>`+renderJobs(state.jobs);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
function renderOrgChart(o){
|
||||
const units=o.units||[];
|
||||
let h=`<div style="background:var(--sb);color:#fff;border-radius:11px;padding:12px 16px;margin-bottom:14px;font-size:13px"><b style="color:var(--mid)">Вывод:</b> ${esc(o.insight||'')}</div>`;
|
||||
h+=units.map(u=>`<div style="background:var(--white);border:1px solid var(--border);border-radius:11px;padding:12px 14px;margin-bottom:8px">
|
||||
<div style="display:flex;align-items:center;gap:9px;flex-wrap:wrap">
|
||||
<span style="font-size:14px;font-weight:800">${esc(u.role||'')}</span>
|
||||
<span style="font-size:11px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 8px;border-radius:5px">${(+u.headcount||1)} чел.</span>
|
||||
${u.reports_to&&u.reports_to!=='—'?`<span style="font-size:11px;color:#9ca3af">↑ ${esc(u.reports_to)}</span>`:'<span style="font-size:10px;font-weight:700;color:#6366F1;background:#EEF2FF;padding:1px 8px;border-radius:5px">руководство</span>'}
|
||||
</div>
|
||||
${(u.owns_functions&&u.owns_functions.length)?`<div style="font-size:11px;color:#6b7280;margin-top:6px">Отвечает: ${u.owns_functions.map(f=>`<span style="background:#F3F4F6;padding:1px 6px;border-radius:4px;margin-right:3px;display:inline-block">${esc(f)}</span>`).join('')}</div>`:''}
|
||||
${u.note?`<div style="font-size:11px;color:#92400E;background:#FEF3C7;border-radius:6px;padding:5px 9px;margin-top:6px">${ic('alert',12)} ${esc(u.note)}</div>`:''}
|
||||
</div>`).join('');
|
||||
return h;
|
||||
}
|
||||
function renderJobs(j){
|
||||
const roles=j.roles||[];
|
||||
return `<div style="font-size:13px;font-weight:800;margin-bottom:10px;display:flex;align-items:center;gap:7px">${ic('clipboard',17)} Должностные инструкции</div>`+roles.map(r=>`<div style="background:var(--white);border:1px solid var(--border);border-radius:11px;padding:14px 16px;margin-bottom:10px">
|
||||
<div style="font-size:14px;font-weight:800">${esc(r.role||'')}</div>
|
||||
<div style="font-size:12px;color:#6b7280;margin:3px 0 9px">${esc(r.purpose||'')}${r.reports_to?` · ↑ ${esc(r.reports_to)}`:''}</div>
|
||||
${(r.responsibilities&&r.responsibilities.length)?`<div style="font-size:11px;font-weight:700;color:#374151;margin-bottom:3px">Зоны ответственности</div><ul style="margin:0 0 9px;padding-left:18px;font-size:12px;color:#4B5563">${r.responsibilities.map(x=>`<li>${esc(x)}</li>`).join('')}</ul>`:''}
|
||||
${(r.kpis&&r.kpis.length)?`<div style="font-size:11px;font-weight:700;color:#047857;margin-bottom:3px">KPI</div><div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:9px">${r.kpis.map(k=>`<span style="font-size:11px;background:#ECFDF5;color:#047857;padding:2px 8px;border-radius:5px">${esc(k)}</span>`).join('')}</div>`:''}
|
||||
${(r.authority&&r.authority.length)?`<div style="font-size:11px;color:#6b7280">Полномочия: ${r.authority.map(a=>esc(a)).join(' · ')}</div>`:''}
|
||||
${r.deviation_note?`<div style="font-size:11px;color:#92400E;background:#FEF3C7;border-radius:6px;padding:5px 9px;margin-top:8px">${ic('alert',12)} Учтено пожелание клиента: ${esc(r.deviation_note)}</div>`:''}
|
||||
</div>`).join('');
|
||||
}
|
||||
function renderSpecPane(){
|
||||
const pad=document.getElementById("specPad");
|
||||
if(!state.spec){
|
||||
if(!state.model){pad.innerHTML=runCard(null,"⚠️","Сначала Анализ","ТЗ собирается из функциональной модели. Перейдите на Этап 4 и постройте модель.","← К анализу",()=>go(4));return}
|
||||
pad.innerHTML=runCard("spec","📋","Техническое задание","Из модели бизнеса Елена спроектирует программу: роли, модули, экраны, данные.","Собрать ТЗ →");return}
|
||||
if(!state.model){pad.innerHTML=runCard(null,ic('alert',30),"Сначала Анализ","ТЗ собирается из функциональной модели. Перейдите на Этап 4 и постройте модель.","← К анализу",()=>go(4));return}
|
||||
pad.innerHTML=runCard("spec",ic('clipboard',30),"Техническое задание","Из модели бизнеса Елена спроектирует программу: роли, модули, экраны, данные.","Собрать ТЗ →");return}
|
||||
pad.innerHTML=renderSpec(state.spec)+renderExportBar();
|
||||
}
|
||||
const EST_STAGES=[
|
||||
{key:"interview", name:"Интервью", icon:ic('message',15), desc:"Диагностика «как есть» — карта проблем"},
|
||||
{key:"methods", name:"Методологии", icon:ic('target',15), desc:"Подбор фреймворков под вашу задачу"},
|
||||
{key:"canvas", name:"Стратегия", icon:ic('chart',15), desc:"Целевая модель процессов (TO-BE)"},
|
||||
{key:"idef0", name:"Функции IDEF0", icon:ic('process',15), desc:"Декомпозиция функций + регламенты"},
|
||||
{key:"org", name:"Организация", icon:ic('team',15), desc:"Оргструктура + должностные инструкции"},
|
||||
{key:"spec", name:"Выдача ТЗ", icon:ic('clipboard',15), desc:"Готовое ТЗ к внедрению"},
|
||||
];
|
||||
function estStageDone(k){
|
||||
if(k==="interview")return (state.messages||[]).length>0;
|
||||
if(k==="methods") return !!state.selection;
|
||||
if(k==="canvas") return !!state.canvas;
|
||||
if(k==="idef0") return !!state.model;
|
||||
if(k==="org") return !!(state.orgchart && state.jobs);
|
||||
if(k==="spec") return !!state.spec;
|
||||
return false;
|
||||
}
|
||||
function renderEstimateCard(){
|
||||
const crm=state.crm||{};
|
||||
const sp=crm.stage_prices;
|
||||
if(!sp)return ''; // смета ещё не сформирована — не показываем
|
||||
const pays=crm.stage_payments||{};
|
||||
const due=crm.stage_due||{};
|
||||
const fmtD=d=>{if(!d)return'';const a=d.split('-');return a[2]+'.'+a[1]+'.'+a[0].slice(2);};
|
||||
const total=Object.values(sp).reduce((a,b)=>a+(+b||0),0);
|
||||
const paid=Object.values(pays).reduce((s,p)=>s+(p.amount||0),0);
|
||||
return `<div class="exp-bar" style="margin-bottom:14px">
|
||||
<div class="exp-h" style="display:flex;align-items:center;gap:7px">${ic('receipt',18)} Смета проекта</div>
|
||||
<div class="exp-d" style="margin-bottom:10px">Прозрачно: за что и сколько. Интервью — бесплатно, дальше помодульно. Готовое ТЗ — после полной оплаты.</div>
|
||||
${EST_STAGES.map(s=>{
|
||||
const price=+sp[s.key]||0, isFree=price<=0, isPaid=!!pays[s.key], done=estStageDone(s.key);
|
||||
const dd=due[s.key]||'';
|
||||
let tag='';
|
||||
if(isFree)tag='<span style="font-size:10px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 7px;border-radius:5px">бесплатно</span>';
|
||||
else if(isPaid)tag='<span style="font-size:10px;font-weight:700;color:#047857;background:#D1FAE5;padding:1px 7px;border-radius:5px">'+ic('check',11)+' оплачено</span>';
|
||||
else if(done)tag='<span style="font-size:10px;font-weight:700;color:#92400E;background:#FEF3C7;padding:1px 7px;border-radius:5px">к оплате</span>';
|
||||
else tag='<span style="font-size:10px;font-weight:700;color:#9CA3AF;background:#F3F4F6;padding:1px 7px;border-radius:5px">в работе</span>';
|
||||
const dueChip=(dd&&!isPaid&&!isFree)?`<span style="font-size:10px;font-weight:600;color:#2563EB;background:#EFF6FF;padding:1px 7px;border-radius:5px">${ic('calendar',11)} до ${fmtD(dd)}</span>`:'';
|
||||
return `<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid rgba(0,0,0,.06)">
|
||||
<span style="font-size:15px">${s.icon}</span>
|
||||
<div style="flex:1;min-width:0"><div style="font-size:13px;font-weight:700;display:flex;align-items:center;gap:7px;flex-wrap:wrap">${s.name} ${tag} ${dueChip}</div><div style="font-size:11px;color:#6b7280;margin-top:1px">${s.desc}</div></div>
|
||||
<span style="font-size:13px;font-weight:700;color:${isFree?'#9CA3AF':'#047857'};white-space:nowrap">${isFree?'0 ₽':money(price)}</span>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
<div style="display:flex;align-items:center;padding:11px 0 2px;border-top:2px solid rgba(0,0,0,.1);margin-top:3px">
|
||||
<span style="font-size:13px;font-weight:800">Итого</span>
|
||||
<span style="margin-left:auto;font-size:17px;font-weight:800;color:#047857">${money(total)}</span>
|
||||
</div>
|
||||
${paid>0?`<div style="font-size:11px;color:#6b7280;margin-top:4px">Оплачено: <b style="color:#047857">${money(paid)}</b> · остаток ${money(Math.max(0,total-paid))}</div>`:''}
|
||||
</div>`;
|
||||
}
|
||||
function renderExportBar(){
|
||||
if(state.unlocked){
|
||||
return `<div class="exp-bar"><div class="exp-h">📦 Готовые документы</div>
|
||||
return renderEstimateCard()+`<div class="exp-bar"><div class="exp-h" style="display:flex;align-items:center;gap:7px">${ic('crm',17)} Готовые документы</div>
|
||||
<div class="exp-d">Долг закрыт — документы и ТЗ доступны для печати и выгрузки.</div>
|
||||
<div class="exp-btns">
|
||||
<button class="btn btn-p" onclick="printDoc()">🖨 Печать / PDF</button>
|
||||
<button class="btn btn-s" onclick="downloadTZ()">⬇ Скачать ТЗ</button>
|
||||
<button class="btn btn-p" onclick="printDoc()">${ic('printer',16)} Печать / PDF</button>
|
||||
<button class="btn btn-s" onclick="downloadTZ()">${ic('download',16)} Скачать ТЗ</button>
|
||||
<button class="btn btn-s" onclick="exportDev()">{ } Выгрузить для разработчика</button>
|
||||
</div></div>`;
|
||||
}
|
||||
const debt=state.debt||0, deal=state.deal_amount||0;
|
||||
const money=deal>0?`Сумма: ${deal.toLocaleString('ru')} ₽${debt>0?` · к оплате: <b>${debt.toLocaleString('ru')} ₽</b>`:''}`:'';
|
||||
return `<div class="exp-bar locked">
|
||||
<div class="exp-h">🔒 Документы готовы — доступны после оплаты</div>
|
||||
<div class="exp-d">Вы видите полный результат на экране. Печатный документ и выгрузка ТЗ для разработчика открываются после закрытия оплаты.<br>${money}</div>
|
||||
const moneyStr=deal>0?`Сумма: ${deal.toLocaleString('ru')} ₽${debt>0?` · к оплате: <b>${debt.toLocaleString('ru')} ₽</b>`:''}`:'';
|
||||
return renderEstimateCard()+`<div class="exp-bar locked">
|
||||
<div class="exp-h" style="display:flex;align-items:center;gap:7px">${ic('lock',17)} Документы готовы — доступны после оплаты</div>
|
||||
<div class="exp-d">Вы видите полный результат на экране. Печатный документ и выгрузка ТЗ для разработчика открываются после закрытия оплаты.<br>${moneyStr}</div>
|
||||
<div class="exp-btns"><button class="btn btn-p" onclick="(window.showPayModal||function(){alert('Оплата скоро будет доступна')})()">Оплатить и забрать документы →</button></div>
|
||||
</div>`;
|
||||
}
|
||||
@ -534,19 +859,21 @@ function buildTZmd(s){
|
||||
(s.roles||[]).forEach(r=>m+=`- **${r.name}** — ${r.does} (доступ: ${r.access})\n`);
|
||||
m+=`\n## B. Модули\n`;(s.modules||[]).forEach(x=>{m+=`### ${x.name} [${x.source_node}]\n${x.purpose}\nЭкраны: ${(x.screens||[]).join(', ')}\nДанные: вход — ${x.inputs_data}; выход — ${x.outputs_data}\n`;(x.rules||[]).forEach(r=>m+=`- правило: ${r}\n`);m+=`\n`});
|
||||
m+=`## C. Модель данных\n`;(s.entities||[]).forEach(e=>{m+=`### ${e.name}\n`;(e.fields||[]).forEach(f=>m+=`- ${f.field}: ${f.type}\n`);if((e.relations||[]).length)m+=`Связи: ${e.relations.join(' · ')}\n`;m+=`Пример: ${e.example}\n\n`});
|
||||
if(state.orgchart&&(state.orgchart.units||[]).length){m+=`\n## D. Оргструктура\n${state.orgchart.insight||''}\n`;state.orgchart.units.forEach(u=>{m+=`- **${u.role}** (${(+u.headcount||1)} чел.)${u.reports_to&&u.reports_to!=='—'?` ↑ ${u.reports_to}`:''}${(u.owns_functions||[]).length?` — отвечает: ${u.owns_functions.join(', ')}`:''}${u.note?` (внимание: ${u.note})`:''}\n`})}
|
||||
if(state.jobs&&(state.jobs.roles||[]).length){m+=`\n## E. Должностные инструкции\n`;state.jobs.roles.forEach(r=>{m+=`### ${r.role}\n${r.purpose||''}${r.reports_to?` (подчинение: ${r.reports_to})`:''}\n`;(r.responsibilities||[]).forEach(x=>m+=`- ${x}\n`);if((r.kpis||[]).length)m+=`KPI: ${r.kpis.join(' · ')}\n`;if((r.authority||[]).length)m+=`Полномочия: ${r.authority.join(' · ')}\n`;if(r.deviation_note)m+=`Внимание — учтено пожелание клиента: ${r.deviation_note}\n`;m+=`\n`})}
|
||||
if((s.open_questions||[]).length){m+=`## Уточнить перед разработкой\n`;s.open_questions.forEach(q=>m+=`- ${q}\n`)}
|
||||
return m;
|
||||
}
|
||||
function dl(name,text,type){const b=new Blob([text],{type:type||'text/plain;charset=utf-8'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),2000)}
|
||||
function printDoc(){if(!state.unlocked)return alert('Доступно после оплаты');window.print()}
|
||||
function downloadTZ(){if(!state.unlocked)return alert('Доступно после оплаты');dl(`ТЗ_${(state.client_name||'проект').replace(/\s+/g,'_')}.md`,buildTZmd(state.spec),'text/markdown;charset=utf-8')}
|
||||
function exportDev(){if(!state.unlocked)return alert('Доступно после оплаты');dl(`ТЗ_${(state.client_name||'проект').replace(/\s+/g,'_')}.json`,JSON.stringify({client:state.client_name,niche:state.niche,model:state.model,spec:state.spec},null,2),'application/json')}
|
||||
function exportDev(){if(!state.unlocked)return alert('Доступно после оплаты');dl(`ТЗ_${(state.client_name||'проект').replace(/\s+/g,'_')}.json`,JSON.stringify({client:state.client_name,niche:state.niche,model:state.model,orgchart:state.orgchart,jobs:state.jobs,spec:state.spec},null,2),'application/json')}
|
||||
function runCard(stage,ic,t,d,btn,custom){
|
||||
const id=stage?`id="rb-${stage}"`:'';
|
||||
const onclick=custom?'':`onclick="runBuild('${stage}')"`;
|
||||
return `<div class="run-card"><div class="run-ic">${ic}</div><div class="run-t">${t}</div><div class="run-d">${d}</div><button class="btn btn-p" ${id} ${custom?'':onclick}>${btn}</button></div>`;
|
||||
}
|
||||
const BUILD={canvas:["build-canvas","canvas"],model:["build-model","model"],spec:["build-spec","spec"]};
|
||||
const BUILD={canvas:["build-canvas","canvas"],model:["build-model","model"],spec:["build-spec","spec"],orgchart:["build-orgchart","orgchart"],jobs:["build-jobs","jobs"]};
|
||||
async function runBuild(stage){
|
||||
const [ep,key]=BUILD[stage];const btn=document.getElementById(`rb-${stage}`);
|
||||
if(btn){btn.disabled=true;btn.innerHTML=`<span class="spin">⏳</span> Елена анализирует...`}
|
||||
@ -567,12 +894,14 @@ function renderIdef(m){const box=(fn,ct,ins,outs,me,id,pct)=>{const C=(ct&&ct.le
|
||||
return h}
|
||||
function renderSpec(s){let h=`<div class="spec-h"><span class="pl">A</span>Обзор</div><div class="blk">${esc(s.overview)}</div>`;
|
||||
h+=`<div class="spec-h"><span class="pl">A</span>Роли (${s.roles.length})</div>`;s.roles.forEach(r=>h+=`<div class="blk" style="padding:12px 15px"><b>${esc(r.name)}</b> — ${esc(r.does)}<div style="font-size:11px;color:#9ca3af;margin-top:3px">Доступ: ${esc(r.access)}</div></div>`);
|
||||
h+=`<div class="spec-h"><span class="pl">B</span>Модули (${s.modules.length})</div>`;s.modules.forEach(m=>h+=`<div class="mod"><div class="mod-top"><span class="mod-node">${esc(m.source_node)}</span><span class="mod-name">${esc(m.name)}</span></div><div style="font-size:12px;color:var(--muted)">${esc(m.purpose)}</div><div style="margin:6px 0">${m.screens.map(s=>`<span class="scr">${esc(s)}</span>`).join("")}</div><div class="mod-data">📥 ${esc(m.inputs_data)} · 📤 ${esc(m.outputs_data)}</div>${m.rules.length?`<div style="margin-top:6px">${m.rules.map(r=>`<div class="blk-pain">${esc(r)}</div>`).join("")}</div>`:''}</div>`);
|
||||
h+=`<div class="spec-h"><span class="pl">C</span>Модель данных (${s.entities.length} таблиц)</div>`;s.entities.forEach(e=>h+=`<div class="ent"><div class="ent-name">◆ ${esc(e.name)}</div><div class="ent-fields">${e.fields.map(f=>`<div class="fld"><b>${esc(f.field)}</b> <em>${esc(f.type)}</em></div>`).join("")}</div>${e.relations.length?`<div style="font-size:11px;color:#6b7280;margin-bottom:6px">🔗 ${e.relations.map(esc).join(" · ")}</div>`:''}<div class="ent-ex">${esc(e.example)}</div></div>`);
|
||||
h+=`<div class="spec-h"><span class="pl">B</span>Модули (${s.modules.length})</div>`;s.modules.forEach(m=>h+=`<div class="mod"><div class="mod-top"><span class="mod-node">${esc(m.source_node)}</span><span class="mod-name">${esc(m.name)}</span></div><div style="font-size:12px;color:var(--muted)">${esc(m.purpose)}</div><div style="margin:6px 0">${m.screens.map(s=>`<span class="scr">${esc(s)}</span>`).join("")}</div><div class="mod-data">Вход: ${esc(m.inputs_data)} · Выход: ${esc(m.outputs_data)}</div>${m.rules.length?`<div style="margin-top:6px">${m.rules.map(r=>`<div class="blk-pain">${esc(r)}</div>`).join("")}</div>`:''}</div>`);
|
||||
h+=`<div class="spec-h"><span class="pl">C</span>Модель данных (${s.entities.length} таблиц)</div>`;s.entities.forEach(e=>h+=`<div class="ent"><div class="ent-name">◆ ${esc(e.name)}</div><div class="ent-fields">${e.fields.map(f=>`<div class="fld"><b>${esc(f.field)}</b> <em>${esc(f.type)}</em></div>`).join("")}</div>${e.relations.length?`<div style="font-size:11px;color:#6b7280;margin-bottom:6px">Связи: ${e.relations.map(esc).join(" · ")}</div>`:''}<div class="ent-ex">${esc(e.example)}</div></div>`);
|
||||
if(s.open_questions&&s.open_questions.length){h+=`<div class="blk" style="border-color:#FDE68A;background:#FFFBEB"><div style="font-weight:700;margin-bottom:8px">Уточнить перед разработкой</div>${s.open_questions.map(q=>`<div class="blk-pain">${esc(q)}</div>`).join("")}</div>`}
|
||||
return h}
|
||||
|
||||
inp.addEventListener("input",()=>{inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"});
|
||||
try{ if(localStorage.getItem('cab_test_hidden')==='1'){var _tb=document.getElementById('testBanner');if(_tb)_tb.style.display='none';}
|
||||
if(localStorage.getItem('cab_hint_hidden')==='1'){var _sh=document.getElementById('startHint');if(_sh)_sh.style.display='none';} }catch(e){}
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -230,6 +230,11 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
<div class="hdr-client" id="hdrClient"></div>
|
||||
<div class="hdr-r"><button onclick="openIdeas()" title="Предложить идею" style="display:inline-flex;align-items:center;gap:6px;background:rgba(16,185,129,.16);color:#34D399;border:1px solid rgba(16,185,129,.32);border-radius:9px;padding:6px 11px;font-size:12px;font-weight:700;font-family:Inter;cursor:pointer"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg><span class="idea-label">Предложить идею</span></button><div class="elena-chip"><div class="elena-av">Е</div><div class="elena-nm">Елена</div><div class="elena-dot"></div></div></div>
|
||||
</header>
|
||||
<div id="testBanner" style="display:flex;align-items:center;gap:10px;padding:9px 16px;background:#ECFDF5;border-bottom:1px solid #A7F3D0;color:#065F46;font-size:12.5px;font-weight:500;line-height:1.45">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0"><path d="M9 2v6.5L4.2 17a2 2 0 0 0 1.8 3h12a2 2 0 0 0 1.8-3L15 8.5V2"/><path d="M8 2h8"/><path d="M7.5 13h9"/></svg>
|
||||
<span><b>Тестовая версия.</b> Сервис в режиме обкатки — данные могут обновляться, это нормально. Спасибо, что тестируете вместе с нами!</span>
|
||||
<button onclick="var b=document.getElementById('testBanner');b.style.display='none';try{localStorage.setItem('cab_test_hidden','1')}catch(e){}" aria-label="Скрыть" style="margin-left:auto;flex-shrink:0;background:none;border:none;color:#047857;font-size:16px;line-height:1;cursor:pointer;padding:2px 4px">✕</button>
|
||||
</div>
|
||||
<div class="pay-banner" id="payBanner"><span style="display:inline-flex;align-items:center;gap:7px"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>К оплате: <b id="payAmount"></b></span><button class="pay-btn" onclick="showPayModal()">Оплатить →</button></div>
|
||||
<div class="modal-bg" id="signModal">
|
||||
<div class="modal" style="max-width:520px">
|
||||
@ -308,6 +313,11 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
<!-- Этап 1 — Разговор с Еленой (знакомство + диагностика) -->
|
||||
<div class="sv active" id="sv1">
|
||||
<div class="hero"><div class="hero-ic"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div><div class="hero-tag">Этап 1 из 4 · В процессе</div><div class="hero-h">Разговор с Еленой</div><div class="hero-d">Расскажите о бизнесе — Елена знакомится и сразу углубляется в детали. Текстом или голосом.</div></div></div>
|
||||
<div id="startHint" style="margin:12px 16px 0;padding:11px 14px;background:#F0FDF4;border:1px solid #BBF7D0;border-radius:11px;font-size:13px;color:#065F46;line-height:1.5;display:flex;gap:9px;align-items:flex-start">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#047857" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;margin-top:1px"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg>
|
||||
<div><b>С чего начать:</b> расскажите Елене о своём бизнесе — чем занимаетесь, сколько человек в команде, что сейчас болит. Можно <b>голосом</b> (кнопка микрофона) или текстом. Хватит 10–15 минут.</div>
|
||||
<button onclick="var h=document.getElementById('startHint');h.style.display='none';try{localStorage.setItem('cab_hint_hidden','1')}catch(e){}" aria-label="Скрыть" style="flex-shrink:0;background:none;border:none;color:#047857;font-size:15px;line-height:1;cursor:pointer;padding:2px 4px">✕</button>
|
||||
</div>
|
||||
<div class="scroll"><div class="chat" id="chat"></div></div>
|
||||
<div class="inbar">
|
||||
<textarea class="inp" id="inp" rows="1" placeholder="Напишите или скажите Елене..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
|
||||
@ -890,6 +900,8 @@ function renderSpec(s){let h=`<div class="spec-h"><span class="pl">A</span>Об
|
||||
return h}
|
||||
|
||||
inp.addEventListener("input",()=>{inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"});
|
||||
try{ if(localStorage.getItem('cab_test_hidden')==='1'){var _tb=document.getElementById('testBanner');if(_tb)_tb.style.display='none';}
|
||||
if(localStorage.getItem('cab_hint_hidden')==='1'){var _sh=document.getElementById('startHint');if(_sh)_sh.style.display='none';} }catch(e){}
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user