<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Newton Academy — Спринт-план Май 2026</title>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#f5f6fa;--white:#fff;--surface:#eef0f7;--border:#dde1ef;
--navy:#1a2150;--navy2:#2d3580;--blue:#3d56f0;--blue-light:#eef0ff;
--green:#0db87f;--green-light:#e6faf4;--amber:#f5a623;--amber-light:#fff8ec;
--red:#e8405a;--red-light:#ffeef1;--purple:#7c3aed;--purple-light:#f3f0ff;
--text:#1a1f36;--text2:#5a6080;--text3:#9aa0bc;
--mono:'JetBrains Mono',monospace;--sans:'Manrope',sans-serif;
}
body{font-family:var(--sans);background:var(--bg);color:var(--text);font-size:14px;line-height:1.6}
.header{background:var(--navy);padding:20px 40px;display:flex;align-items:center;justify-content:space-between}
.header-logo{color:#fff;font-size:20px;font-weight:800}.header-logo span{color:#7b93ff}
.header-meta{font-family:var(--mono);font-size:11px;color:rgba(255,255,255,.4);letter-spacing:.1em;text-transform:uppercase}
.tabs-wrap{background:var(--white);border-bottom:1px solid var(--border);padding:0 40px;position:sticky;top:0;z-index:100;box-shadow:0 2px 12px rgba(26,33,80,.06)}
.tabs{display:flex}
.tab{padding:16px 24px;font-size:14px;font-weight:600;color:var(--text2);cursor:pointer;border-bottom:3px solid transparent;transition:all .2s;background:none;border-top:none;border-left:none;border-right:none;font-family:var(--sans)}
.tab.active{color:var(--blue);border-bottom-color:var(--blue)}
.tab:hover:not(.active){color:var(--text);border-bottom-color:var(--border)}
.city{display:none;padding:28px 40px 60px;max-width:1200px;margin:0 auto}
.city.active{display:block}
.sec-label{font-family:var(--mono);font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--blue);margin-bottom:8px}
.sec-title{font-size:22px;font-weight:800;color:var(--navy);margin-bottom:20px}
/* EDITABLE */
[data-ce]{cursor:text;border-radius:3px;outline:none;transition:background .15s}
[data-ce]:hover{background:rgba(61,86,240,.04);outline:1px dashed #c9d0ff}
[data-ce]:focus{background:var(--blue-light)!important;outline:2px solid var(--blue)!important}
/* DELETABLE BLOCK */
.dblock{position:relative}
.dblock-btn{position:absolute;top:-9px;right:-9px;width:22px;height:22px;border-radius:50%;background:var(--red);color:#fff;border:none;cursor:pointer;font-size:11px;display:none;align-items:center;justify-content:center;z-index:20;line-height:1;box-shadow:0 1px 4px rgba(232,64,90,.4)}
.dblock:hover>.dblock-btn{display:flex}
/* METRICS */
.metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px}
.metric{background:var(--white);border:1px solid var(--border);border-radius:12px;padding:18px 20px;position:relative}
.metric-val{font-family:var(--mono);font-size:24px;font-weight:600;line-height:1;margin-bottom:4px}
.metric-label{font-size:11px;color:var(--text2);font-weight:500}
.metric-sub{font-size:10px;color:var(--text3);margin-top:4px;font-family:var(--mono)}
.metric.blue .metric-val{color:var(--blue)}.metric.green .metric-val{color:var(--green)}
.metric.amber .metric-val{color:var(--amber)}.metric.purple .metric-val{color:var(--purple)}
/* PLAN */
.plan-section{background:var(--white);border:1px solid var(--border);border-radius:16px;padding:22px 26px;margin-bottom:28px;position:relative}
.plan-row{display:grid;grid-template-columns:140px 1fr 180px;align-items:center;gap:14px;margin-bottom:12px}
.plan-label{font-size:12px;font-weight:600;color:var(--text2)}
.plan-track{height:10px;background:var(--surface);border-radius:99px;overflow:hidden}
.plan-fill{height:100%;border-radius:99px;transition:width .6s ease}
.plan-fill.blue{background:var(--blue)}.plan-fill.green{background:var(--green)}
.plan-fill.amber{background:var(--amber)}.plan-fill.purple{background:var(--purple)}
.plan-nums{font-family:var(--mono);font-size:12px;color:var(--text2);text-align:right;white-space:nowrap;display:flex;align-items:center;justify-content:flex-end;gap:2px}
.plan-inp{font-family:var(--mono);font-size:12px;color:var(--text2);background:transparent;border:none;outline:none;cursor:text;width:auto;text-align:right;min-width:10px}
.plan-inp.actual{font-size:13px;color:var(--text);font-weight:700;max-width:70px}
.plan-inp.target{max-width:60px}
.plan-inp:focus{background:var(--blue-light);border-radius:3px;padding:0 2px;color:var(--blue)}
.plan-pct{font-family:var(--mono);font-size:11px;color:var(--text3);margin-left:4px}
.plan-note{margin-top:10px;font-size:11px;color:var(--text3)}
/* INSIGHT */
.insight{background:var(--blue-light);border:1px solid #c9d0ff;border-radius:10px;padding:14px 18px;font-size:12px;color:var(--navy2);margin-bottom:20px;line-height:1.6;position:relative}
/* BOARD */
.board-hint{font-size:11px;color:var(--text3);margin-bottom:14px;padding:8px 14px;background:var(--white);border:1px solid var(--border);border-radius:8px;display:inline-block}
.sprints{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-bottom:28px}
.sprint{background:var(--white);border:1px solid var(--border);border-radius:16px;overflow:hidden;display:flex;flex-direction:column;position:relative}
.sprint-head{padding:14px 18px;border-bottom:1px solid var(--border)}
.sprint-badge{display:inline-block;font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;padding:2px 9px;border-radius:99px;margin-bottom:6px}
.sprint-badge.s1{background:var(--blue-light);color:var(--blue)}
.sprint-badge.s2{background:var(--green-light);color:var(--green)}
.sprint-badge.s3{background:var(--amber-light);color:#b37300}
.sprint-title{font-size:14px;font-weight:700;color:var(--navy);margin-bottom:1px}
.sprint-dates{font-size:11px;color:var(--text3);font-family:var(--mono)}
.sprint-focus{font-size:11px;color:var(--text2);font-style:italic;margin-top:5px;padding-top:6px;border-top:1px solid var(--surface)}
.sprint-body{padding:10px 10px 6px;flex:1;min-height:50px;transition:background .12s}
.sprint-body.drag-over{background:#eef0ff}
.sprint-del-btn{position:absolute;top:8px;right:10px;background:none;border:none;cursor:pointer;color:var(--text3);font-size:13px;opacity:0;transition:opacity .15s;padding:2px 5px;border-radius:5px;z-index:5}
.sprint:hover .sprint-del-btn{opacity:1}
.sprint-del-btn:hover{background:var(--red-light);color:var(--red)}
/* TASK */
.task-card{background:var(--white);border:1px solid var(--border);border-radius:9px;margin-bottom:6px;display:flex;align-items:flex-start;transition:box-shadow .15s,border-color .15s;position:relative}
.task-card.dragging{opacity:.3}
.task-card.drag-ghost{border:2px dashed var(--blue);background:var(--blue-light);height:40px;pointer-events:none}
.task-card:hover{box-shadow:0 2px 10px rgba(61,86,240,.1);border-color:#b8c0f0}
.task-card.completed .task-text{text-decoration:line-through;color:var(--text3)}
.task-check{width:14px;height:14px;border-radius:3px;border:1.5px solid var(--border);background:var(--white);flex-shrink:0;margin:10px 0 0 9px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:9px;transition:all .15s;user-select:none}
.task-card.completed .task-check{background:var(--green);border-color:var(--green);color:#fff}
.task-handle{padding:9px 5px 9px 7px;color:var(--text3);font-size:13px;cursor:grab;flex-shrink:0;user-select:none}
.task-handle:hover{color:var(--blue)}
.task-inner{flex:1;padding:7px 4px 7px 0;min-width:0}
.task-row1{display:flex;align-items:center;gap:5px;margin-bottom:3px;flex-wrap:wrap}
.ch-badge{display:inline-flex;align-items:center;font-family:var(--mono);font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px;white-space:nowrap}
.ch-badge.zv{background:#f0faf0;color:#1a7a3a}.ch-badge.ig{background:#fce8f3;color:#a62d78}
.ch-badge.yt{background:#fff0ee;color:#c0392b}.ch-badge.off{background:#f5f0ff;color:#6d28d9}
.ch-badge.tg{background:#e8f4ff;color:#0077b6}
.task-kpi{font-family:var(--mono);font-size:10px;font-weight:600;padding:1px 6px;border-radius:5px;white-space:nowrap;min-width:20px}
.task-kpi.blue{background:var(--blue-light);color:var(--blue)}.task-kpi.green{background:var(--green-light);color:#0a8f60}
.task-kpi.amber{background:var(--amber-light);color:#8a6000}.task-kpi.red{background:var(--red-light);color:#b52e45}
.task-kpi.purple{background:var(--purple-light);color:var(--purple)}.task-kpi.gray{background:var(--surface);color:var(--text2)}
.task-kpi:focus{outline:2px solid var(--blue);border-radius:3px}
.task-text{font-size:12px;color:var(--text);line-height:1.45;outline:none;display:block}
.task-text:focus{outline:2px solid var(--blue);border-radius:3px;padding:0 2px}
.task-del{width:22px;height:22px;border:none;background:none;cursor:pointer;color:var(--text3);font-size:12px;flex-shrink:0;margin:6px 6px 0 0;border-radius:5px;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s,background .15s}
.task-card:hover .task-del{opacity:1}
.task-del:hover{background:var(--red-light);color:var(--red)}
.add-task-btn{width:100%;padding:7px;border:1.5px dashed var(--border);border-radius:9px;background:transparent;color:var(--text3);font-size:12px;cursor:pointer;font-family:var(--sans);transition:all .15s;margin-top:4px;display:flex;align-items:center;justify-content:center;gap:5px}
.add-task-btn:hover{border-color:var(--blue);color:var(--blue);background:var(--blue-light)}
.sprint-count{font-family:var(--mono);font-size:10px;color:var(--text3);text-align:right;padding:4px 10px 8px;display:block}
/* HYP */
.hyp-section{background:var(--white);border:1px solid var(--border);border-radius:16px;padding:22px 26px;margin-bottom:28px;position:relative}
.hyp-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:14px}
.hyp-card{background:var(--surface);border-radius:10px;padding:14px;position:relative}
.hyp-sprint{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--text3);margin-bottom:6px}
.hyp-title{font-size:12px;font-weight:700;color:var(--navy);margin-bottom:7px}
.hyp-item{font-size:11px;color:var(--text2);padding:4px 0;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;gap:8px;position:relative}
.hyp-item:last-child{border-bottom:none}
.hyp-exp{font-family:var(--mono);font-size:10px;color:var(--green);font-weight:600;white-space:nowrap}
.hyp-item-del{background:none;border:none;cursor:pointer;color:var(--text3);font-size:10px;padding:1px 3px;border-radius:3px;opacity:0;flex-shrink:0;transition:opacity .15s}
.hyp-item:hover .hyp-item-del{opacity:1}
.hyp-item-del:hover{background:var(--red-light);color:var(--red)}
.add-hyp-btn{width:100%;padding:5px;border:1px dashed var(--border);border-radius:6px;background:transparent;color:var(--text3);font-size:11px;cursor:pointer;font-family:var(--sans);transition:all .15s;margin-top:6px}
.add-hyp-btn:hover{border-color:var(--blue);color:var(--blue);background:var(--blue-light)}
@media(max-width:900px){.sprints,.hyp-grid,.metrics-grid{grid-template-columns:1fr}.city{padding:16px}.header{padding:16px 20px}}
</style>
</head>
<body>
<div class="header">
<div class="header-logo">Newton <span>Academy</span></div>
<div class="header-meta">Спринт-план · Май 2026 · 13–31 мая</div>
</div>
<div class="tabs-wrap">
<div class="tabs">
<button class="tab active" onclick="showCity('tashkent',this)">🇺🇿 Ташкент</button>
<button class="tab" onclick="showCity('almaty',this)">🇰🇿 Алматы</button>
<button class="tab" onclick="showCity('astana',this)">🇰🇿 Астана</button>
</div>
</div>
<div id="tashkent" class="city active"><div id="pg-tashkent"></div><div id="board-tashkent"></div></div>
<div id="almaty" class="city"><div id="pg-almaty"></div><div id="board-almaty"></div></div>
<div id="astana" class="city"><div id="pg-astana"></div><div id="board-astana"></div></div>
<script>
// ═══════════════════════════════════════════
// PAGE CONTENT DEFAULTS
// ═══════════════════════════════════════════
const PG_DEF = {
tashkent:{
aprTitle:'Апрель 2026 — Ташкент',
metrics:[
{val:'404',label:'Лидов всего',sub:'Недели: 61/130/115/98',cls:'blue'},
{val:'127',label:'SQL (квал.)',sub:'~31.4% квалификация',cls:'purple'},
{val:'40',label:'Продажи (клиенты)',sub:'CR лид→оплата 9.9%',cls:'green'},
{val:'175.6M',label:'Выручка (сум)',sub:'65 встреч проведено',cls:'amber'},
],
insightApr:'📌 Ключевой инсайт апреля: Ташкент показывает уверенные результаты: 127 SQL из 404 лидов (31.4% квалификации), 40 продаж, CR лид→оплата 9.9%. Главный фокус мая — звонобот на 870 лидов за 3 спринта и запуск YouTube-контента для органической лидогенерации.',
mayTitle:'Цели и прогресс',
plan:[
{label:'Лиды',target:'850',actual:'265',color:'blue'},
{label:'SQL',target:'250',actual:'80',color:'purple'},
{label:'Клиенты',target:'30',actual:'3',color:'green'},
{label:'Выручка',target:'250M',actual:'33M',color:'amber'},
],
planNote:'* Данные на 13 мая. Недели 1–2 закрыты. Спринты 1–3 (13–31 мая) — впереди.',
hypTitle:'Гипотезы по контенту — план 45 лидов из органики',
hypInsight:'📌 Базовый инсайт: 87% постов без CTA. Контент работает как «витрина», не как воронка. CTA + бот + лид-магнит = рост лидов из контента в 3–5 раз без увеличения бюджета.',
hyps:[
{sprint:'Спринт 1 · 13–17 мая',title:'Воронка и три темы',items:[
{text:'CTA в каждый пост (кнопка «записаться»)',exp:'+30-50%'},
{text:'Stories: кейс поступления в Президентскую — до/после',exp:'3–5 лидов'},
{text:'Reels «Почемучки» — контент для детей 5–6 лет',exp:'охват ×1.5'},
{text:'Пост «Как поступить в Президентскую — гайд»',exp:'3–5 лидов'},
{text:'🎯 Цель Sprint 1',exp:'10–12 лидов',bold:true},
]},
{sprint:'Спринт 2 · 19–23 мая',title:'Доверие и соц. доказательство',items:[
{text:'Видеоотзыв родителя (60 сек) воскресенье — пик ×2',exp:'5–7 лидов'},
{text:'Карусель «Как поступить в Президентскую — 5 шагов»',exp:'3–5 лидов'},
{text:'Разбор задания из вступительного в Президентскую',exp:'2–4 лида'},
{text:'Reels с учителем: подтягиваем предмет за 60 сек',exp:'охват ×3'},
{text:'🎯 Цель Sprint 2',exp:'15–17 лидов',bold:true},
]},
{sprint:'Спринт 3 · 26–31 мая',title:'Президентские + летний набор',items:[
{text:'YouTube кейс ученика → бесплатная диагностика',exp:'4–6 лидов'},
{text:'Telegram: «10 задач из вступительного в Президентскую»',exp:'3–5 лидов'},
{text:'Reels «Почемучки»: как подготовить ребёнка к 1 классу',exp:'органик охват'},
{text:'Посев в родительских чатах (5 чатов × 2 поста)',exp:'+60–80 охват'},
{text:'🎯 Цель Sprint 3',exp:'16–18 лидов',bold:true},
]},
],
deleted:{},
},
almaty:{
aprTitle:'Апрель 2026 — Алматы',
metrics:[
{val:'142',label:'Лидов всего',sub:'Недели: 47/27/34/34',cls:'blue'},
{val:'37',label:'SQL (квал.)',sub:'26.1% квалификация',cls:'purple'},
{val:'11',label:'Продажи (клиенты)',sub:'CR лид→оплата 7.75%',cls:'green'},
{val:'7.04M',label:'Выручка (тенге)',sub:'12 встреч проведено',cls:'amber'},
],
insightApr:'',
mayTitle:'Цели и прогресс',
plan:[
{label:'Лиды',target:'120',actual:'28',color:'blue'},
{label:'SQL',target:'45',actual:'8',color:'purple'},
{label:'Клиенты',target:'14',actual:'1',color:'green'},
{label:'Выручка',target:'9M',actual:'673K',color:'amber'},
],
planNote:'* Лиды плана = ~40/нед × 3 спринта.',
hypTitle:'Гипотезы по контенту — план 20 лидов из органики',
hypInsight:'📌 Стратегия Алматы: Главный актив — реальные результаты НИШ/БИЛ. Новый СММщик + видеомейкер стартуют с правила 3-3-1 и CTA в каждом посте. Воскресенье = пик вовлечённости (×2).',
hyps:[
{sprint:'Спринт 1 · 13–17 мая',title:'Старт трёх тем',items:[
{text:'CTA «Диагностика» в каждый пост с первого дня',exp:'+30-50%'},
{text:'Stories: кейс поступления в НИШ/БИЛ — до/после',exp:'3–5 лидов'},
{text:'Reels «Почемучки» — подготовка к школе для дошколят',exp:'охват ×1.5'},
{text:'Reels с учителем: подтягиваем предмет за 60 сек',exp:'охват ×3'},
{text:'🎯 Цель Sprint 1',exp:'3–5 лидов',bold:true},
]},
{sprint:'Спринт 2 · 19–23 мая',title:'НИШ-волна + знания',items:[
{text:'Серия «Ученики, сдавшие ЕНТ» — воскресный постинг',exp:'7–10 лидов'},
{text:'Reels: разбор задачи НИШ-вступительного (учитель)',exp:'4–6 лидов'},
{text:'Карусель «Подтяни предмет за 4 недели»',exp:'сохранения ↑'},
{text:'Правило 3-3-1: 3 обучающих + 3 доверия + 1 оффер',exp:'системно'},
{text:'🎯 Цель Sprint 2',exp:'8–10 лидов',bold:true},
]},
{sprint:'Спринт 3 · 26–31 мая',title:'НИШ-итоги + лето + почемучки',items:[
{text:'Серия «Наши ученики сдали ЕНТ» — часть 2 + отзывы',exp:'5–7 лидов'},
{text:'Видеоотзыв: ребёнок поступил в НИШ — мама делится',exp:'3–5 лидов'},
{text:'Reels «Почемучки»: как подготовить к 1 классу',exp:'2–4 лида'},
{text:'Посев в родительских чатах (5 чатов × 2 поста)',exp:'+50–80 охват'},
{text:'🎯 Цель Sprint 3',exp:'7–9 лидов',bold:true},
]},
],
deleted:{},
},
astana:{
aprTitle:'Апрель 2026 — Астана',
metrics:[
{val:'249',label:'Лидов всего',sub:'Недели: 73/68/47/61',cls:'blue'},
{val:'121',label:'SQL (квал.)',sub:'48.6% квалификация 🏆',cls:'purple'},
{val:'23',label:'Продажи (клиенты)',sub:'CR лид→оплата 9.24%',cls:'green'},
{val:'13.5M',label:'Выручка (тенге)',sub:'28 встреч проведено',cls:'amber'},
],
insightApr:'🏆 Ключевой инсайт апреля: Астана — лидер по квалификации лидов (48.6% vs 26% Алматы). Конверсия приход→оплата 82% — хорошо. Слабое место — «Запись→Приход» 75.7%: каждый 4-й не приходит. Май: фокус на снижение no-show через ремайндеры и прогрев в Telegram.',
mayTitle:'Цели и прогресс',
plan:[
{label:'Лиды',target:'180',actual:'51',color:'blue'},
{label:'SQL',target:'85',actual:'20',color:'purple'},
{label:'Клиенты',target:'18',actual:'2',color:'green'},
{label:'Выручка',target:'11M',actual:'1.44M',color:'amber'},
],
planNote:'* Лиды плана = ~60/нед × 3 спринта.',
hypTitle:'Гипотезы по контенту — план 25 лидов из органики',
hypInsight:'📌 Стратегия Астана: Самый сильный рынок по квалификации (48.6%). Поход в БИЛ + ЕНТ-результаты — уникальный контент с вирусным потенциалом. Приоритет: видеоотзывы НИШ/БИЛ как главный конвертер.',
hyps:[
{sprint:'Спринт 1 · 13–17 мая',title:'Старт трёх тем',items:[
{text:'CTA «Диагностика» в каждый пост с первого дня',exp:'+30-50%'},
{text:'Stories: кейс поступления в НИШ/БИЛ — до/после',exp:'3–5 лидов'},
{text:'Reels «Почемучки» — развивающий контент для дошколят',exp:'охват ×1.5'},
{text:'Reels с учителем: подтягиваем предмет за 60 сек',exp:'охват ×3'},
{text:'🎯 Цель Sprint 1',exp:'5–7 лидов',bold:true},
]},
{sprint:'Спринт 2 · 19–23 мая',title:'БИЛ-вирус + НИШ-кейсы',items:[
{text:'Контент с похода в БИЛ — уникальный, вирусный',exp:'органик охват'},
{text:'Видеоотзывы НИШ в воскресенье (пик ×2 вовлечённость)',exp:'8–10 лидов'},
{text:'Карусель «Подтяни предмет за 4 недели»',exp:'сохранения ↑'},
{text:'Правило 3-3-1: 3 обучающих + 3 доверия + 1 оффер',exp:'системно'},
{text:'🎯 Цель Sprint 2',exp:'10–12 лидов',bold:true},
]},
{sprint:'Спринт 3 · 26–31 мая',title:'НИШ-итоги + лето + почемучки',items:[
{text:'Серия «Наши ученики сдали ЕНТ» — часть 2 воскресенье',exp:'7–9 лидов'},
{text:'Видеоотзыв: ребёнок поступил в НИШ/БИЛ — мама делится',exp:'3–5 лидов'},
{text:'Reels «Почемучки»: как подготовить к 1 классу',exp:'2–4 лида'},
{text:'Посев в родительских чатах (5 чатов × 2 поста)',exp:'+50–80 охват'},
{text:'🎯 Цель Sprint 3',exp:'8–10 лидов',bold:true},
]},
],
deleted:{},
},
};
// ═══════════════════════════════════════════
// PAGE STATE (localStorage)
// ═══════════════════════════════════════════
const _pg = {};
function gpg(city){
if(!_pg[city]){
try{ const s=localStorage.getItem('nt_pg_'+city); _pg[city]=s?JSON.parse(s):JSON.parse(JSON.stringify(PG_DEF[city])); }
catch(e){ _pg[city]=JSON.parse(JSON.stringify(PG_DEF[city])); }
}
return _pg[city];
}
function spg(city){ try{ localStorage.setItem('nt_pg_'+city,JSON.stringify(_pg[city])); }catch(e){} }
// ═══════════════════════════════════════════
// PAGE RENDERING
// ═══════════════════════════════════════════
function parseNum(s){
s=String(s).trim().toUpperCase().replace(/\s/g,'');
if(s.endsWith('M'))return parseFloat(s)*1e6;
if(s.endsWith('K'))return parseFloat(s)*1e3;
return parseFloat(s)||0;
}
function calcPct(actual,target){
const a=parseNum(actual),t=parseNum(target);
if(!t)return 0;
return Math.round((a/t)*100);
}
function ce(el,getter,setter,city){
el.setAttribute('contenteditable','true');
el.setAttribute('data-ce','1');
el.spellcheck=false;
el.addEventListener('blur',()=>{ setter(el.textContent.trim()||getter()); spg(city); });
el.addEventListener('keydown',e=>{ if(e.key==='Enter'){e.preventDefault();el.blur();} });
}
function delBtn(onClick){
const b=document.createElement('button');
b.className='dblock-btn';b.textContent='×';b.title='Удалить блок';
b.addEventListener('click',e=>{e.stopPropagation();onClick();});
return b;
}
function renderPage(city){
const wrap=document.getElementById('pg-'+city);
wrap.innerHTML='';
const pg=gpg(city);
const del=pg.deleted||{};
// ── April title
const aprLabel=document.createElement('div');aprLabel.className='sec-label';aprLabel.textContent='Отчёт за прошлый месяц';
wrap.appendChild(aprLabel);
const aprTitle=document.createElement('div');aprTitle.className='sec-title';aprTitle.textContent=pg.aprTitle;
ce(aprTitle,()=>pg.aprTitle,(v)=>{pg.aprTitle=v;},city);
wrap.appendChild(aprTitle);
// ── Metrics
if(!del['metrics']){
const grid=document.createElement('div');grid.className='metrics-grid dblock';
const mDelBtn=delBtn(()=>{ pg.deleted['metrics']=true;spg(city);renderPage(city); });
grid.appendChild(mDelBtn);
(pg.metrics||[]).forEach((m,i)=>{
const card=document.createElement('div');card.className='metric '+m.cls+' dblock';
const cDelBtn=delBtn(()=>{ pg.metrics.splice(i,1);spg(city);renderPage(city); });
card.appendChild(cDelBtn);
const val=document.createElement('div');val.className='metric-val';val.textContent=m.val;
ce(val,()=>m.val,(v)=>{m.val=v;},city);
const lbl=document.createElement('div');lbl.className='metric-label';lbl.textContent=m.label;
ce(lbl,()=>m.label,(v)=>{m.label=v;},city);
const sub=document.createElement('div');sub.className='metric-sub';sub.textContent=m.sub;
ce(sub,()=>m.sub,(v)=>{m.sub=v;},city);
card.appendChild(val);card.appendChild(lbl);card.appendChild(sub);
grid.appendChild(card);
});
wrap.appendChild(grid);
}
// ── April insight (optional)
if(!del['insight-apr'] && pg.insightApr){
const ins=document.createElement('div');ins.className='insight dblock';
const iDelBtn=delBtn(()=>{ pg.deleted['insight-apr']=true;spg(city);renderPage(city); });
ins.appendChild(iDelBtn);
const txt=document.createElement('span');txt.textContent=pg.insightApr;
ce(txt,()=>pg.insightApr,(v)=>{pg.insightApr=v;},city);
ins.appendChild(txt);
wrap.appendChild(ins);
}
// ── May plan
if(!del['plan']){
const mayLabel=document.createElement('div');mayLabel.className='sec-label';mayLabel.style.marginTop='4px';mayLabel.textContent='Май 2026 — план vs факт';
wrap.appendChild(mayLabel);
const mayTitle=document.createElement('div');mayTitle.className='sec-title';mayTitle.textContent=pg.mayTitle;
ce(mayTitle,()=>pg.mayTitle,(v)=>{pg.mayTitle=v;},city);
wrap.appendChild(mayTitle);
const planSec=document.createElement('div');planSec.className='plan-section dblock';
const pDelBtn=delBtn(()=>{ pg.deleted['plan']=true;spg(city);renderPage(city); });
planSec.appendChild(pDelBtn);
(pg.plan||[]).forEach((row,i)=>{
const r=document.createElement('div');r.className='plan-row';
// Label
const lbl=document.createElement('div');lbl.className='plan-label';lbl.textContent=row.label;
ce(lbl,()=>row.label,(v)=>{row.label=v;},city);
r.appendChild(lbl);
// Track
const track=document.createElement('div');track.className='plan-track';
const fill=document.createElement('div');fill.className='plan-fill '+row.color;
const pct=Math.min(calcPct(row.actual,row.target),100);
fill.style.width=pct+'%';
track.appendChild(fill);r.appendChild(track);
// Nums
const nums=document.createElement('div');nums.className='plan-nums';
const actInp=document.createElement('input');actInp.className='plan-inp actual';actInp.type='text';
actInp.value=row.actual;actInp.title='Фактическое значение (редактируй)';
actInp.size=Math.max(3,String(row.actual).length);
actInp.addEventListener('input',()=>{
row.actual=actInp.value;spg(city);
const p=Math.min(calcPct(actInp.value,tgtInp.value),100);
fill.style.width=p+'%';
pctSpan.textContent='= '+calcPct(actInp.value,tgtInp.value)+'%';
actInp.size=Math.max(3,actInp.value.length);
});
actInp.addEventListener('blur',()=>{row.actual=actInp.value;spg(city);});
const sep=document.createElement('span');sep.textContent=' / ';sep.style.color='var(--text3)';
const tgtInp=document.createElement('input');tgtInp.className='plan-inp target';tgtInp.type='text';
tgtInp.value=row.target;tgtInp.title='Плановое значение';
tgtInp.size=Math.max(3,String(row.target).length);
tgtInp.addEventListener('input',()=>{
row.target=tgtInp.value;spg(city);
const p=Math.min(calcPct(actInp.value,tgtInp.value),100);
fill.style.width=p+'%';
pctSpan.textContent='= '+calcPct(actInp.value,tgtInp.value)+'%';
tgtInp.size=Math.max(3,tgtInp.value.length);
});
tgtInp.addEventListener('blur',()=>{row.target=tgtInp.value;spg(city);});
const pctSpan=document.createElement('span');pctSpan.className='plan-pct';
pctSpan.textContent='= '+calcPct(row.actual,row.target)+'%';
nums.appendChild(actInp);nums.appendChild(sep);nums.appendChild(tgtInp);nums.appendChild(pctSpan);
r.appendChild(nums);
planSec.appendChild(r);
});
const note=document.createElement('div');note.className='plan-note';note.textContent=pg.planNote;
ce(note,()=>pg.planNote,(v)=>{pg.planNote=v;},city);
planSec.appendChild(note);
wrap.appendChild(planSec);
}
// ── Sprint board label
const sbLabel=document.createElement('div');sbLabel.className='sec-label';sbLabel.textContent='Спринт-план';
wrap.appendChild(sbLabel);
const sbTitle=document.createElement('div');sbTitle.className='sec-title';sbTitle.textContent='Задачи 13–31 мая по спринтам';
ce(sbTitle,()=>'Задачи 13–31 мая по спринтам',(v)=>{},city);
wrap.appendChild(sbTitle);
// ── Hypotheses
if(!del['hyp']){
const hypSec=document.createElement('div');hypSec.className='hyp-section dblock';
const hDelBtn=delBtn(()=>{ pg.deleted['hyp']=true;spg(city);renderPage(city); });
hypSec.appendChild(hDelBtn);
const hLabel=document.createElement('div');hLabel.className='sec-label';hLabel.textContent='Лидогенерация через контент';
hypSec.appendChild(hLabel);
const hTitle=document.createElement('div');hTitle.className='sec-title';hTitle.style.marginBottom='8px';hTitle.textContent=pg.hypTitle;
ce(hTitle,()=>pg.hypTitle,(v)=>{pg.hypTitle=v;},city);
hypSec.appendChild(hTitle);
if(pg.hypInsight){
const hIns=document.createElement('div');hIns.className='insight';hIns.style.marginBottom='14px';
hIns.textContent=pg.hypInsight;
ce(hIns,()=>pg.hypInsight,(v)=>{pg.hypInsight=v;},city);
hypSec.appendChild(hIns);
}
const hypGrid=document.createElement('div');hypGrid.className='hyp-grid';
(pg.hyps||[]).forEach((card,ci)=>{
const hCard=document.createElement('div');hCard.className='hyp-card dblock';
const hcDel=delBtn(()=>{ pg.hyps.splice(ci,1);spg(city);renderPage(city); });
hCard.appendChild(hcDel);
const sprintEl=document.createElement('div');sprintEl.className='hyp-sprint';sprintEl.textContent=card.sprint;
ce(sprintEl,()=>card.sprint,(v)=>{card.sprint=v;},city);
hCard.appendChild(sprintEl);
const titleEl=document.createElement('div');titleEl.className='hyp-title';titleEl.textContent=card.title;
ce(titleEl,()=>card.title,(v)=>{card.title=v;},city);
hCard.appendChild(titleEl);
(card.items||[]).forEach((item,ii)=>{
const row=document.createElement('div');
row.className='hyp-item'+(item.bold?' ':' ');
if(item.bold) row.style.fontWeight='600';
const textSpan=document.createElement('span');textSpan.style.flex='1';textSpan.textContent=item.text;
ce(textSpan,()=>item.text,(v)=>{item.text=v;},city);
const expSpan=document.createElement('span');expSpan.className='hyp-exp';expSpan.textContent=item.exp;
ce(expSpan,()=>item.exp,(v)=>{item.exp=v;},city);
const iDel=document.createElement('button');iDel.className='hyp-item-del';iDel.textContent='×';
iDel.title='Удалить';
iDel.addEventListener('click',e=>{ e.stopPropagation();card.items.splice(ii,1);spg(city);renderPage(city); });
row.appendChild(textSpan);row.appendChild(expSpan);row.appendChild(iDel);
hCard.appendChild(row);
});
// Add hyp item button
const addHyp=document.createElement('button');addHyp.className='add-hyp-btn';addHyp.textContent='+ Добавить гипотезу';
addHyp.addEventListener('click',()=>{
card.items.push({text:'Новая гипотеза',exp:'KPI',bold:false});
spg(city);renderPage(city);
});
hCard.appendChild(addHyp);
hypGrid.appendChild(hCard);
});
hypSec.appendChild(hypGrid);
wrap.appendChild(hypSec);
}
}
// ═══════════════════════════════════════════
// SPRINT METADATA & TASK DEFAULTS
// ═══════════════════════════════════════════
const SM={tashkent:[{key:'s1',badge:'s1',label:'Спринт 1',title:'Старт + звонобот',dates:'13–17 мая (пн–пт)',focus:'Фокус: запустить звонобот, закрыть апрельский долг, снять срочные видеокреативы'},{key:'s2',badge:'s2',label:'Спринт 2',title:'Контент + кастдев',dates:'19–23 мая (пн–пт)',focus:'Фокус: YouTube-ролики, съёмка экосистемы, кастдев, запуск звонобота'},{key:'s3',badge:'s3',label:'Спринт 3',title:'Финиш + офлайн/сайт',dates:'26–31 мая (пн–сб)',focus:'Фокус: YouTube-кейсы, офлайн-брендинг, кастдев итог, звонобот'}],almaty:[{key:'s1',badge:'s1',label:'Спринт 1',title:'Онбординг + запуск',dates:'13–17 мая (пн–пт)',focus:'Фокус: онбординг команды, закреп, 2ГИС, первые съёмки, звонобот'},{key:'s2',badge:'s2',label:'Спринт 2',title:'НИШ-контент + БИЛ',dates:'19–23 мая (пн–пт)',focus:'Фокус: съёмка учителей, НИШ-отзывы, поход БИЛ, выкладка ЕНТ (ч. 1)'},{key:'s3',badge:'s3',label:'Спринт 3',title:'Итоги + дизайн + кастдев',dates:'26–31 мая (пн–сб)',focus:'Фокус: кастдев итог, ОП Фест, 2ГИС отчёт 6 мес., новый тест-дизайн'}],astana:[{key:'s1',badge:'s1',label:'Спринт 1',title:'Запуск + онбординг',dates:'13–17 мая (пн–пт)',focus:'Фокус: звонобот, онбординг команды, ChatPlace, 2ГИС, первые съёмки'},{key:'s2',badge:'s2',label:'Спринт 2',title:'БИЛ-поход + НИШ-контент',dates:'19–23 мая (пн–пт)',focus:'Фокус: поход на экзамены БИЛ, съёмка учителей, кастдев, ЕНТ-контент'},{key:'s3',badge:'s3',label:'Спринт 3',title:'Кастдев + офлайн + итоги',dates:'26–31 мая (пн–сб)',focus:'Фокус: закрыть оба кастдева, БИЛ-видео, вывеска, 2ГИС 6 мес.'}]};
const BN={zv:'Звонобот',ig:'Видео/SMM',yt:'YouTube',off:'Офлайн',tg:'Telegram'};
const DT={tashkent:{s1:[{id:'ts1a',b:'zv',t:'Запуск звонобота: Чт 850к + Пт 850к',k:'290 лидов',kc:'blue'},{id:'ts1b',b:'yt',t:'Ролик о школе Аль-Хорезми',k:'1 ролик',kc:'gray'},{id:'ts1c',b:'off',t:'Договориться со спецшколами о съёмках, запланировать даты',k:'3+ школы',kc:'amber'},{id:'ts1d',b:'zv',t:'Финальный отчёт по звоноботу за апрель и май',k:'Сдан до пт',kc:'gray'},{id:'ts1e',b:'zv',t:'Решить вопрос возврата средств по звоноботу',k:'Закрыть',kc:'red'},{id:'ts1f',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'ts1g',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}],s2:[{id:'ts2a',b:'zv',t:'Запуск звонобота 2,000,000 сум',k:'290 лидов',kc:'blue'},{id:'ts2b',b:'yt',t:'Информационный ролик для YouTube',k:'Снят + смонт.',kc:'gray'},{id:'ts2c',b:'yt',t:'Стоковые материалы: видео + фотосет',k:'30+ фото / 10+ видео',kc:'gray'},{id:'ts2d',b:'yt',t:'Ролик «Экосистема Ньютон»: T-Shaped, работа с семьёй, CORE, отчёты, атмосфера',k:'1 ролик готов',kc:'amber'},{id:'ts2e',b:'ig',t:'Съёмка нового филиала Чиланзар: Reels + фото статика',k:'3 Reels + 15 фото',kc:'purple'},{id:'ts2f',b:'',t:'📋 Кастдев 60 чел. (первые 30 интервью)',k:'30 интервью',kc:'green'},{id:'ts2g',b:'',t:'🔄 Настроить процесс сборки отзывов и кейсов',k:'Процесс запущен',kc:'gray'},{id:'ts2h',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'ts2i',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}],s3:[{id:'ts3a',b:'zv',t:'Запуск звонобота 2,000,000 сум',k:'290 лидов',kc:'blue'},{id:'ts3b',b:'yt',t:'Ролик «Крутой учитель» — раскрыть преподавателя (кто, откуда, сколько лет)',k:'Снят + загружен',kc:'gray'},{id:'ts3c',b:'ig',t:'Отзывы для лендинга и страниц',k:'5+ отзывов',kc:'green'},{id:'ts3d',b:'yt',t:'Ролик «Кейс ученика»: точка А → Б, результаты, любимый учитель',k:'Снят + загружен',kc:'amber'},{id:'ts3e',b:'',t:'🌐 Переделка сайта Newton: новый дизайн, обновить инфо',k:'Сайт обновлён',kc:'amber'},{id:'ts3f',b:'',t:'📣 Начать работу с пиарщиком по ситуации с kun.uz',k:'Пиарщик найден',kc:'red'},{id:'ts3g',b:'off',t:'Замена логотипа на всех филиалах. Утверждение вывесок и плакатов',k:'Все филиалы',kc:'gray'},{id:'ts3h',b:'',t:'📊 Платформа Parvoz по президентским школам',k:'Запущена',kc:'blue'},{id:'ts3i',b:'',t:'📋 Кастдев 60 чел. (вторые 30 интервью) + отчёт',k:'Отчёт готов',kc:'green'},{id:'ts3j',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'ts3k',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}]},almaty:{s1:[{id:'as1a',b:'zv',t:'Запуск звонобота Алматы — 1 раз',k:'По плану лидов',kc:'blue'},{id:'as1b',b:'',t:'🎓 Онбординг СММщика и видеомейкера',k:'Завершён к пт',kc:'amber'},{id:'as1c',b:'',t:'📋 Презентация для СММ: новая стратегия работы',k:'Проведена',kc:'green'},{id:'as1d',b:'ig',t:'Обновить закреплённые посты на основном аккаунте',k:'Закреп готов',kc:'gray'},{id:'as1e',b:'off',t:'Смена оффера 2ГИС + отчёт за прошлую неделю',k:'Еженедельно',kc:'gray'},{id:'as1f',b:'ig',t:'Организация и съёмка обзора филиалов в Алматы',k:'1+ ролик обзора',kc:'gray'},{id:'as1g',b:'',t:'⚙️ Актуализировать все оптимизации в ChatPlace',k:'Готово',kc:'gray'},{id:'as1h',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'as1i',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}],s2:[{id:'as2a',b:'zv',t:'Запуск звонобота Алматы — 1 раз',k:'По плану лидов',kc:'blue'},{id:'as2b',b:'ig',t:'Видеоотзывы НИШ: ученики и родители (Алматы)',k:'3+ отзыва снято',kc:'green'},{id:'as2c',b:'ig',t:'С 2 учителями по 5 видео: знакомство + разбор задач + отзывы',k:'10 видео',kc:'purple'},{id:'as2d',b:'ig',t:'Выкладка части 1 — ученики, сдавшие майское ЕНТ',k:'5+ постов',kc:'green'},{id:'as2e',b:'',t:'📋 Кастдев Алматы 4–5–6 классы 60 чел. (первые 30)',k:'30 интервью',kc:'amber'},{id:'as2f',b:'off',t:'Смена оффера 2ГИС + отчёт за неделю',k:'Еженедельно',kc:'gray'},{id:'as2g',b:'',t:'🛠 Разработать новый сервис PDF-результатов диагностики',k:'ТЗ готово',kc:'blue'},{id:'as2h',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'as2i',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}],s3:[{id:'as3a',b:'ig',t:'С учителем 5 видео: знакомство + разбор + отзывы',k:'5 видео',kc:'purple'},{id:'as3b',b:'ig',t:'Выкладка части 2 — ученики, сдавшие майское ЕНТ',k:'5+ постов',kc:'green'},{id:'as3c',b:'ig',t:'Монтаж и выкладка материалов с похода БИЛ — 5 видео',k:'5 видео',kc:'amber'},{id:'as3d',b:'',t:'🎁 Закуп подарков для поступивших в НИШ',k:'Куплено',kc:'gray'},{id:'as3e',b:'',t:'🏆 ОП Фест: сформировать презентацию + заказать дипломы',k:'Готово к фесту',kc:'amber'},{id:'as3f',b:'',t:'📋 Кастдев Алматы 4–5–6 (вторые 30) + отчёт',k:'60 интервью + отчёт',kc:'green'},{id:'as3g',b:'',t:'🎨 Заменить дизайн диагностических тестов',k:'Новый дизайн',kc:'blue'},{id:'as3h',b:'off',t:'Отчёт по 2ГИС Алматы за 6 мес. + презентация + план работы с KPI',k:'Презентация готова',kc:'amber'},{id:'as3i',b:'',t:'🔧 Решить блокер: неправильные данные в аналитике',k:'Закрыть',kc:'red'},{id:'as3j',b:'',t:'🔄 Наладить процесс сбора кейсов и отзывов',k:'Процесс описан',kc:'gray'},{id:'as3k',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'as3l',b:'off',t:'Смена оффера 2ГИС + отчёт за неделю',k:'Еженедельно',kc:'gray'},{id:'as3m',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}]},astana:{s1:[{id:'ns1a',b:'zv',t:'Запуск звонобота Астана — 1 раз',k:'По плану лидов',kc:'blue'},{id:'ns1b',b:'',t:'🎓 Онбординг СММщика и видеомейкера',k:'Завершён к пт',kc:'amber'},{id:'ns1c',b:'',t:'📋 Презентация для СММ: новая стратегия',k:'Проведена',kc:'green'},{id:'ns1d',b:'ig',t:'Обновить закреплённые посты на основном аккаунте',k:'Закреп готов',kc:'gray'},{id:'ns1e',b:'off',t:'Смена оффера 2ГИС + отчёт за прошлую неделю',k:'Еженедельно',kc:'gray'},{id:'ns1f',b:'',t:'⚙️ Актуализировать все оптимизации в ChatPlace',k:'Готово',kc:'gray'},{id:'ns1g',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'ns1h',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}],s2:[{id:'ns2a',b:'zv',t:'Запуск звонобота Астана — 2 раза',k:'По плану лидов',kc:'blue'},{id:'ns2b',b:'off',t:'Поход на экзамены в БИЛ (Астана) — съёмка контента',k:'Контент с экзаменов',kc:'amber'},{id:'ns2c',b:'ig',t:'Видеоотзывы НИШ: ученики и родители (Астана)',k:'3+ отзыва снято',kc:'green'},{id:'ns2d',b:'ig',t:'С 2 учителями по 5 видео: знакомство + разбор + отзывы',k:'10 видео',kc:'purple'},{id:'ns2e',b:'ig',t:'Выкладка части 1 — ученики, сдавшие майское ЕНТ (Астана)',k:'5+ постов',kc:'green'},{id:'ns2f',b:'',t:'📋 Кастдев Астана ЕНТ 60 чел. (первые 30)',k:'30 интервью',kc:'amber'},{id:'ns2g',b:'off',t:'Смена оффера 2ГИС + отчёт за неделю',k:'Еженедельно',kc:'gray'},{id:'ns2h',b:'',t:'🛠 Разработать новый сервис PDF-результатов диагностики (совм. с Алматы)',k:'ТЗ готово',kc:'blue'},{id:'ns2i',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'ns2j',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}],s3:[{id:'ns3a',b:'zv',t:'Запуск звонобота Астана — 1 раз',k:'По плану лидов',kc:'blue'},{id:'ns3b',b:'ig',t:'Выкладка части 2 — ученики, сдавшие майское ЕНТ (Астана)',k:'5+ постов',kc:'green'},{id:'ns3c',b:'ig',t:'Монтаж и выкладка материалов с похода БИЛ — 5 видео',k:'5 видео',kc:'amber'},{id:'ns3d',b:'ig',t:'С учителем 5 видео: знакомство + разбор + отзывы',k:'5 видео',kc:'purple'},{id:'ns3e',b:'',t:'🎁 Закуп подарков для поступивших в НИШ (совм. с Алматы)',k:'Куплено',kc:'gray'},{id:'ns3f',b:'',t:'🏆 ОП Фест: презентация + дипломы (совм. с Алматы)',k:'Готово к фесту',kc:'amber'},{id:'ns3g',b:'',t:'📋 Кастдев Астана ЕНТ (вторые 30) + отчёт',k:'60 инт. + отчёт',kc:'green'},{id:'ns3h',b:'',t:'📋 Кастдев Астана 4–5–6 классы 60 чел. + отчёт',k:'60 инт. + отчёт',kc:'green'},{id:'ns3i',b:'',t:'🎨 Обновление дизайна вывески на Мангилик Ел',k:'Обновлено',kc:'blue'},{id:'ns3j',b:'',t:'🎨 Заменить дизайн диагностических тестов',k:'Новый дизайн',kc:'blue'},{id:'ns3k',b:'off',t:'Отчёт 2ГИС Алматы 6 мес. + презентация + план с KPI (совм.)',k:'Презентация готова',kc:'amber'},{id:'ns3l',b:'',t:'🔄 Наладить процесс сбора кейсов и отзывов',k:'Процесс описан',kc:'gray'},{id:'ns3m',b:'',t:'🔧 Решить блокер: неправильные данные в аналитике',k:'Закрыть',kc:'red'},{id:'ns3n',b:'ig',t:'Видеокреативы: 2 говорящая голова + 2 отзыва + 1 ИИ-ролик',k:'5 видео',kc:'purple'},{id:'ns3o',b:'off',t:'Смена оффера 2ГИС + отчёт за неделю',k:'Еженедельно',kc:'gray'},{id:'ns3p',b:'',t:'🎓 Обучение для команды',k:'1 сессия',kc:'gray'}]}};
// ═══════════════════════════════════════════
// TASK BOARD (unchanged logic, preserves nt_{city} localStorage)
// ═══════════════════════════════════════════
const _st={};
function gs(city){if(!_st[city]){try{const s=localStorage.getItem('nt_'+city);_st[city]=s?JSON.parse(s):JSON.parse(JSON.stringify(DT[city]));}catch(e){_st[city]=JSON.parse(JSON.stringify(DT[city]));}}return _st[city];}
function sv(city){try{localStorage.setItem('nt_'+city,JSON.stringify(_st[city]));}catch(e){}}
function ft(state,id){for(const k of Object.keys(state)){const i=state[k].findIndex(x=>x.id===id);if(i>=0)return{k,i,t:state[k][i]};}return null;}
let dCity=null,dSprint=null,dId=null,ghost=null;
function rb(city){
const wrap=document.getElementById('board-'+city);
wrap.innerHTML='<div class="board-hint">⠿ Перетаскивай задачи между спринтами · Кликни текст или KPI — редактируй · ✕ удалить · ☑ выполнено</div>';
const grid=document.createElement('div');grid.className='sprints';
SM[city].forEach(sp=>{
const state=gs(city);const tasks=state[sp.key]||[];const dc=tasks.filter(t=>t.done).length;
const col=document.createElement('div');col.className='sprint';
// Sprint delete button
const sdBtn=document.createElement('button');sdBtn.className='sprint-del-btn';sdBtn.textContent='✕';sdBtn.title='Удалить спринт';
sdBtn.addEventListener('click',()=>{
if(!confirm('Удалить все задачи спринта «'+sp.title+'»?'))return;
const st=gs(city);st[sp.key]=[];sv(city);rb(city);
});
col.appendChild(sdBtn);
const head=document.createElement('div');head.className='sprint-head';
head.innerHTML=`<div class="sprint-badge ${sp.badge}">${sp.label}</div><div class="sprint-title" data-ce="1" contenteditable spellcheck="false">${sp.title}</div><div class="sprint-dates">${sp.dates}</div><div class="sprint-focus" data-ce="1" contenteditable spellcheck="false">${sp.focus}</div>`;
col.appendChild(head);
const body=document.createElement('div');body.className='sprint-body';body.dataset.city=city;body.dataset.sprint=sp.key;
tasks.forEach(task=>body.appendChild(mc(task,city,sp.key)));
const btn=document.createElement('button');btn.className='add-task-btn';btn.textContent='+ Добавить задачу';
btn.addEventListener('click',()=>{
const st=gs(city);const nt={id:city+'_'+sp.key+'_'+Date.now(),b:'',t:'Новая задача',k:'KPI',kc:'gray',done:false};
st[sp.key].push(nt);sv(city);rb(city);
setTimeout(()=>{const cards=wrap.querySelectorAll('[data-sprint="'+sp.key+'"] .task-card');const last=cards[cards.length-1];if(last){const te=last.querySelector('.task-text');if(te){te.focus();document.execCommand('selectAll',false,null);}}},40);
});
body.appendChild(btn);
const ctr=document.createElement('span');ctr.className='sprint-count';
ctr.textContent=dc>0?`✓ ${dc} / ${tasks.length}`:`${tasks.length} задач`;body.appendChild(ctr);
body.addEventListener('dragover',e=>{e.preventDefault();body.classList.add('drag-over');if(!ghost){ghost=document.createElement('div');ghost.className='task-card drag-ghost';}const af=ga(body,e.clientY);af?body.insertBefore(ghost,af):body.insertBefore(ghost,btn);});
body.addEventListener('dragleave',e=>{if(!body.contains(e.relatedTarget))body.classList.remove('drag-over');});
body.addEventListener('drop',e=>{
e.preventDefault();body.classList.remove('drag-over');if(!dId)return;
const tc=body.dataset.city,ts=body.dataset.sprint;
const prevCity=dCity,prevSprint=dSprint,prevId=dId;
const ss=gs(prevCity),tts=gs(tc);
const srcList=ss[prevSprint];const idx=srcList.findIndex(x=>x.id===prevId);if(idx<0){dId=null;return;}
const mv=srcList.splice(idx,1)[0];const tgtList=tts[ts];let ins=tgtList.length;
if(ghost&&ghost.parentNode===body){let c=0;for(let i=0;i<body.children.length;i++){if(body.children[i]===ghost){ins=c;break;}if(body.children[i].classList.contains('task-card')&&!body.children[i].classList.contains('drag-ghost'))c++;}}
tgtList.splice(ins,0,mv);sv(prevCity);if(tc!==prevCity)sv(tc);
dId=null;dCity=null;dSprint=null;if(ghost&&ghost.parentNode)ghost.parentNode.removeChild(ghost);ghost=null;
rb(city);if(tc!==city)rb(tc);if(prevCity!==city&&prevCity!==tc)rb(prevCity);
});
col.appendChild(body);grid.appendChild(col);
});
wrap.appendChild(grid);
}
function mc(task,city,sk){
const el=document.createElement('div');
el.className='task-card'+(task.done?' completed':'');
el.draggable=false;
const chk=document.createElement('div');chk.className='task-check';chk.textContent=task.done?'✓':'';chk.title='Отметить выполненным';
chk.addEventListener('click',e=>{e.stopPropagation();const s=gs(city);const r=ft(s,task.id);if(r){r.t.done=!r.t.done;sv(city);rb(city);}});
const hnd=document.createElement('div');hnd.className='task-handle';hnd.textContent='⠿';
hnd.addEventListener('mousedown',()=>{ el.draggable=true; });
const inn=document.createElement('div');inn.className='task-inner';
const r1=document.createElement('div');r1.className='task-row1';
if(task.b&&BN[task.b]){const bd=document.createElement('span');bd.className='ch-badge '+task.b;bd.textContent=BN[task.b];r1.appendChild(bd);}
const kpi=document.createElement('span');kpi.className='task-kpi '+task.kc;
kpi.setAttribute('contenteditable','true');kpi.spellcheck=false;kpi.textContent=task.k;
kpi.addEventListener('mousedown',e=>e.stopPropagation());
kpi.addEventListener('focus',()=>{ el.draggable=false; });
kpi.addEventListener('blur',()=>{const v=kpi.textContent.trim();const s=gs(city);const r=ft(s,task.id);if(r&&v){r.t.k=v;sv(city);}});
kpi.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();kpi.blur();}});
r1.appendChild(kpi);inn.appendChild(r1);
const txt=document.createElement('div');txt.className='task-text';
txt.setAttribute('contenteditable','true');txt.spellcheck=false;txt.textContent=task.t;
txt.addEventListener('mousedown',e=>e.stopPropagation());
txt.addEventListener('focus',()=>{ el.draggable=false; });
txt.addEventListener('blur',()=>{const v=txt.textContent.trim();const s=gs(city);const r=ft(s,task.id);if(r&&v){r.t.t=v;sv(city);}});
txt.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();txt.blur();}});
inn.appendChild(txt);
const del=document.createElement('button');del.className='task-del';del.textContent='✕';del.title='Удалить';
del.addEventListener('click',e=>{e.stopPropagation();if(!confirm('Удалить задачу?'))return;const s=gs(city);const r=ft(s,task.id);if(r){s[r.k].splice(r.i,1);sv(city);rb(city);}});
el.appendChild(chk);el.appendChild(hnd);el.appendChild(inn);el.appendChild(del);
el.addEventListener('dragstart',e=>{if(!el.draggable){e.preventDefault();return;}dCity=city;dSprint=sk;dId=task.id;el.classList.add('dragging');e.dataTransfer.effectAllowed='move';e.dataTransfer.setData('text/plain',task.id);setTimeout(()=>{ el.style.opacity='.3'; },0);});
el.addEventListener('dragend',()=>{el.draggable=false;el.classList.remove('dragging');el.style.opacity='';if(ghost&&ghost.parentNode)ghost.parentNode.removeChild(ghost);ghost=null;document.querySelectorAll('.sprint-body').forEach(b=>b.classList.remove('drag-over'));dId=null;dCity=null;dSprint=null;});
return el;
}
function ga(container,y){
const els=[...container.querySelectorAll('.task-card:not(.dragging):not(.drag-ghost)')];
return els.reduce((cl,el)=>{const box=el.getBoundingClientRect();const off=y-box.top-box.height/2;if(off<0&&off>cl.offset)return{offset:off,el};return cl;},{offset:-Infinity}).el;
}
function showCity(id,btn){
document.querySelectorAll('.city').forEach(c=>c.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
document.getElementById(id).classList.add('active');btn.classList.add('active');
window.scrollTo({top:0,behavior:'smooth'});
}
document.addEventListener('mouseup',()=>{document.querySelectorAll('.task-card').forEach(c=>{c.draggable=false;});});
['tashkent','almaty','astana'].forEach(c=>{renderPage(c);rb(c);});
</script>
</body>
</html>