35c30ecce0
F-CORPUS: ключевые документы приёмки liderra.ru лежали untracked — мастер- хэндофф ссылался на отсутствующие в git файлы (битые ссылки в новом клоне). Закоммичены: R0–R5 + stepbystep ранбуки, хартия, prod-logic-map, эфир-хэндофф, imitation-checks-table, live-demo/ (эфир-плеер) + смежные specs/планы серий f1-card/phase1/televizor/g1/g2 (решение владельца — «корпус + смежные»). F-DELPROJ: пункт №15 checks-table → «удаление проекта со сделками запрещено (422), сделки целы» (было неточно «сделки сохранены», сверено по ProjectService::delete). Реестр находок: статусы F-DEPTRAC/F-CSV/F-REMIND/F-DELPROJ/F-CORPUS → закрыто. .gitleaks.toml: ранбуки приёмки добавлены в allowlist (синтетические тест- телефоны, та же категория что plans/specs/audits). live-demo HTML: stylelint --fix (#fff→#ffffff). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
7.6 KiB
HTML
90 lines
7.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Лидерра — приёмка · 7 кабинетов + кино + ИТОГ</title>
|
|
<style>
|
|
:root{--teal:#0F6E56;--ivory:#F6F3EC;--noir:#012019;--amber:#7a4b12;}
|
|
*{box-sizing:border-box;} html,body{margin:0;}
|
|
body{background:var(--noir);color:var(--ivory);font-family:Inter,Arial,sans-serif;font-size:12px;}
|
|
header{background:var(--teal);padding:8px 14px;display:flex;align-items:center;gap:10px;}
|
|
.dot{width:11px;height:11px;border-radius:50%;background:#e5484d;animation:pulse 1.4s infinite;}
|
|
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(229,72,77,.7)}70%{box-shadow:0 0 0 9px rgba(229,72,77,0)}100%{box-shadow:0 0 0 0 rgba(229,72,77,0)}}
|
|
h1{font-size:15px;margin:0;font-weight:600;} .badge{margin-left:auto;font-size:12px;font-weight:700;padding:3px 10px;border-radius:12px;background:#e5484d;} .badge.paused{background:#caa23a;color:#1a1a1a;}
|
|
.bar{background:#06281f;border-bottom:1px solid var(--teal);padding:8px 14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
|
|
.bar button{background:var(--teal);color:#ffffff;border:0;border-radius:6px;padding:7px 12px;font-size:15px;cursor:pointer;} .bar button:hover{background:#13845f;}
|
|
#slider{flex:1 1 200px;min-width:150px;} #pos{font-size:12px;opacity:.85;}
|
|
.risks{display:flex;flex-wrap:wrap;gap:6px;padding:8px 12px;background:#04221a;border-bottom:1px solid var(--teal);}
|
|
.chip{padding:5px 9px;border-radius:13px;background:#16463a;color:#8fbfb0;font-size:12px;border:1px solid #1d5a49;transition:all .25s;}
|
|
.chip.ok{background:#0F6E56;color:#ffffff;font-weight:700;border-color:#19a079;box-shadow:0 0 8px rgba(25,160,121,.5);}
|
|
.prog{padding:8px 14px;font-size:13px;font-weight:700;white-space:pre-wrap;} .prog.kino{background:#1a4a8a;} .prog.itog{background:#0d6b4f;}
|
|
.wrap{display:flex;gap:10px;padding:10px;align-items:flex-start;}
|
|
.col{display:flex;flex-direction:column;gap:10px;min-width:0;} .main{flex:1 1 60%;} .side{flex:1 1 40%;min-width:280px;}
|
|
.panel{background:#06281f;border:1px solid var(--teal);border-radius:7px;overflow:hidden;}
|
|
.panel h2{margin:0;font-size:12px;padding:6px 11px;background:var(--teal);font-weight:600;}
|
|
.panel.itog h2{background:#0d6b4f;font-size:14px;} .panel.why h2{background:var(--amber);} .panel.load h2{background:var(--amber);}
|
|
.cabs{display:flex;flex-wrap:wrap;gap:7px;padding:9px;}
|
|
.cab{flex:1 1 30%;min-width:140px;background:#0a3328;border:2px solid #15463a;border-radius:6px;overflow:hidden;transition:border-color .2s;}
|
|
.cab.hit{border-color:#e5b53a;box-shadow:0 0 8px rgba(229,181,58,.4);}
|
|
.cab .top{background:#0F6E56;font-size:11px;font-weight:700;padding:3px 7px;display:flex;justify-content:space-between;}
|
|
.cab .bod{padding:6px 7px;font-family:"JetBrains Mono",monospace;font-size:11px;line-height:1.45;}
|
|
.cab .bal{color:#9fe3c8;font-weight:700;} .cab .nw{color:#ffd766;}
|
|
pre{margin:0;padding:11px 12px;font-family:"JetBrains Mono",Consolas,monospace;font-size:12.5px;line-height:1.5;white-space:pre-wrap;color:#eafff7;}
|
|
pre.why{font-size:12px;color:#ffe9c2;}
|
|
img{width:100%;max-height:20vh;object-fit:contain;object-position:top;background:#ffffff;display:block;}
|
|
@media(max-width:820px){.wrap{flex-direction:column}.cab{flex:1 1 46%}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header><span class="dot"></span><h1>Лидерра — приёмка · 7 кабинетов + кино + стоп-кадр ИТОГА</h1><span class="badge" id="badge">🔴 LIVE</span></header>
|
|
<div class="bar">
|
|
<button id="first">⏮</button><button id="back">◀</button><button id="play">⏸ Пауза</button><button id="fwd">▶</button><button id="liveBtn">⏭ LIVE</button>
|
|
<input type="range" id="slider" min="1" max="1" value="1"><span id="pos">— / —</span>
|
|
</div>
|
|
<div class="risks" id="risks"></div>
|
|
<div class="prog" id="prog">…</div>
|
|
<div class="wrap">
|
|
<div class="col main">
|
|
<div class="panel"><h2>🟢 7 кабинетов клиентов (проекты · сделки · баланс)</h2><div class="cabs" id="cabs"></div></div>
|
|
<div class="panel itog"><h2 id="rh">📋 ИТОГ темы</h2><pre id="result">…</pre></div>
|
|
<div class="panel why"><h2>❓ ПОЧЕМУ так (правило/формула)</h2><pre id="why" class="why">…</pre></div>
|
|
</div>
|
|
<div class="col side">
|
|
<div class="panel load"><h2>🖥 Сервер + канал (live)</h2><pre id="load">…</pre></div>
|
|
<div class="panel"><h2>🟠 Поставщик crm.bp-gr.ru (реальный экран)</h2><img id="shot" src="supplier.png" alt="поставщик"></div>
|
|
<div class="panel"><h2>🎬 Кино-лог (что я делаю)</h2><pre id="ev">…</pre></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
let total=0,cur=1,live=true;
|
|
const $=id=>document.getElementById(id),pad=n=>String(n).padStart(4,'0');
|
|
async function ft(u){try{const r=await fetch(u+'?t='+Date.now());return r.ok?await r.text():'';}catch(e){return'';}}
|
|
function parse(t){const p={};let c=null,b=[];t.split(/\r?\n/).forEach(l=>{const m=l.match(/^@@(\w+)@@$/);if(m){if(c)p[c]=b.join('\n');c=m[1];b=[];}else b.push(l);});if(c)p[c]=b.join('\n');return p;}
|
|
function badge(){const e=$('badge');if(live){e.textContent='🔴 LIVE';e.className='badge';}else{e.textContent='⏸ ПАУЗА';e.className='badge paused';}$('play').textContent=live?'⏸ Пауза':'▶ Играть';}
|
|
function risks(t){const box=$('risks');box.innerHTML='';(t||'').split(/\r?\n/).filter(Boolean).forEach(l=>{const[lab,st]=l.split('|');const c=document.createElement('span');c.className='chip'+(st==='ok'?' ok':'');c.textContent=(st==='ok'?'✅ ':'⬜ ')+(lab||'');box.appendChild(c);});}
|
|
function cabs(t){const box=$('cabs');box.innerHTML='';(t||'').split(/\r?\n/).filter(Boolean).forEach(l=>{
|
|
const[n,bal,proj,d1,d2,f]=l.split('|');const el=document.createElement('div');el.className='cab'+(f==='new'?' hit':'');
|
|
el.innerHTML='<div class="top"><span>'+(n||'')+'</span><span class="bal">'+(bal||'')+'</span></div><div class="bod">проект: '+(proj||'—')+'<br>'+(f==='new'?'<span class="nw">'+(d1||'')+' ←новая</span>':(d1||''))+'<br>'+(d2||'')+'</div>';
|
|
box.appendChild(el);});}
|
|
async function render(i){
|
|
if(i<1||i>total)return;const p=parse(await ft('frames/'+pad(i)+'.txt'));
|
|
risks(p.RISKS);cabs(p.CABS);
|
|
const itog=(p.PHASE||'').trim()==='itog';
|
|
$('prog').className='prog '+(itog?'itog':'kino');$('prog').textContent=p.PROG||'';
|
|
$('rh').textContent=itog?('📋 ИТОГ: '+(p.THEME||'')):'🎬 идёт прогон темы…';
|
|
$('result').textContent=p.RESULT||'';$('why').textContent=p.WHY||'';$('ev').textContent=p.EV||'';
|
|
$('slider').value=i;$('pos').textContent=i+' / '+total+(live?' (эфир)':' (пауза)');
|
|
}
|
|
async function poll(){const c=parseInt(await ft('count.txt'))||0;if(c>0){total=c;$('slider').max=c;}$('load').textContent=await ft('load.txt');$('shot').src='supplier.png?t='+Date.now();if(live&&total>0){cur=total;render(cur);}}
|
|
$('play').onclick=()=>{live=!live;if(live)cur=total;badge();render(cur);};
|
|
$('back').onclick=()=>{live=false;cur=Math.max(1,cur-1);badge();render(cur);};
|
|
$('fwd').onclick=()=>{live=false;cur=Math.min(total,cur+1);badge();render(cur);};
|
|
$('first').onclick=()=>{live=false;cur=1;badge();render(cur);};
|
|
$('liveBtn').onclick=()=>{live=true;cur=total;badge();render(cur);};
|
|
$('slider').oninput=e=>{live=false;cur=parseInt(e.target.value);badge();render(cur);};
|
|
badge();setInterval(poll,1000);poll();
|
|
</script>
|
|
</body>
|
|
</html>
|