(function(){
var root=document.querySelector('[data-tma-codex]'); if(!root) return;
if(root.dataset.init==='1') return; root.dataset.init='1';
/* ===== PERSISTENCE ===== */
var storeOK=true; try{localStorage.setItem('_probe','1');localStorage.removeItem('_probe');}catch(e){storeOK=false;}
var KEY_CATALOG='TMA_SEMKO_CATALOG_V1';
var KEY_GRID='TMA_SEMKO_GRID_V1';
var defaults=[
{key:'lettuce',name:'Салат латук',secs:150,img:null},
{key:'romaine',name:'Салат ромэн',secs:160,img:null},
{key:'basil',name:'Базилик генуэзский',secs:150,img:null},
{key:'mint',name:'Мята',secs:180,img:null},
{key:'cilantro',name:'Кинза',secs:162,img:null},
{key:'arugula',name:'Руккола',secs:140,img:null}
];
function loadCatalog(){
if(!storeOK) return defaults.slice();
try{
var raw=localStorage.getItem(KEY_CATALOG);
if(!raw) return defaults.slice();
var custom=JSON.parse(raw)||[];
var names=new Set(custom.map(c=>c.name));
return custom.concat(defaults.filter(d=>!names.has(d.name)));
}catch(e){return defaults.slice();}
}
function saveCatalog(list){
if(!storeOK) return;
try{
var custom=list.filter(c=>c.key && c.key.startsWith && c.key.startsWith('semko_') || c.img);
localStorage.setItem(KEY_CATALOG, JSON.stringify(custom));
}catch(e){}
}
var CELLS=12;
function blankGrid(){return Array.from({length:CELLS},()=>({crop:null,name:null,secs:150,img:null,plantedAt:null,boostW:0,boostL:0,boostC:0,water:0}))}
function loadGrid(){
if(!storeOK) return blankGrid();
try{
var raw=localStorage.getItem(KEY_GRID);
if(!raw) return blankGrid();
var g=JSON.parse(raw); if(!Array.isArray(g)||g.length!==CELLS) return blankGrid();
return g;
}catch(e){return blankGrid();}
}
function saveGrid(g){ if(!storeOK) return; try{localStorage.setItem(KEY_GRID, JSON.stringify(g));}catch(e){} }
var catalog=loadCatalog();
var gridState=loadGrid();
var selected=null;
/* ===== REFS ===== */
var elCat=root.querySelector('[data-catalog]');
var elGrid=root.querySelector('[data-grid]');
var elLog=root.querySelector('[data-log]');
var elETA=root.querySelector('[data-eta]');
var sPlanted=root.querySelector('[data-s-planted]');
var sReady=root.querySelector('[data-s-ready]');
var sWater=root.querySelector('[data-s-water]');
/* ===== Helpers ===== */
function ringBG(p){var x=Math.max(0,Math.min(100,Math.floor(p)));return 'conic-gradient(var(--ok) '+x+'%, #eef2f7 0)';}
function log(text){var time=new Date().toLocaleTimeString();var d=document.createElement('div');d.className='line';d.textContent='['+time+'] '+text; if(elLog.firstChild) elLog.insertBefore(d, elLog.firstChild); else elLog.appendChild(d);}
function progress(c){ if(!c || !c.crop || !c.plantedAt) return 0; var e=(Date.now()-c.plantedAt)/1000; var m=1 + (c.boostW>Date.now()? .25:0) + (c.boostL>Date.now()? .25:0) + (c.boostC>Date.now()? .10:0); return Math.min(1,(e*m)/((c.secs||150))); }
function pct(i){ var c = (typeof i === 'number') ? gridState[i] : i; return progress(c)*100; }
function stage(i){var p=pct(i); if(p>=100) return 'Готово'; if(p>=50) return 'Растёт'; if(p>0) return 'Всходы'; return 'Пусто';}
function boosts(c){ if(!c || !c.crop) return '—'; var n=Date.now(); return (c.boostW>n?'Вода+':'Вода—')+' | '+(c.boostL>n?'Свет+':'Свет—')+' | '+(c.boostC>n?'Уборка+':'Уборка—');}
// helper to safely create CSS url from user input
function cssUrl(u){
try{ return 'url("'+encodeURI(String(u))+'")'; }catch(e){ return 'none'; }
}
function updateStats(){
var planted=gridState.filter(c=>!!c.crop).length;
var ready=gridState.filter((c,i)=>c.crop && pct(i)>=100).length;
var water=gridState.reduce((a,c)=>a+(c.water||0),0);
sPlanted.textContent=planted; sReady.textContent=ready; sWater.textContent=water;
var best=null;
var now = Date.now();
gridState.forEach(function(c){
if(!c || !c.crop) return;
if(!c.plantedAt) return;
var elapsed=(now - c.plantedAt)/1000;
var m = 1 + (c.boostW>now? .25:0) + (c.boostL>now? .25:0) + (c.boostC>now? .10:0);
if(progress(c)>=1) return; // already ready
var remaining = Math.max(0, ((c.secs||150) - elapsed) / (m||1));
if(best === null || remaining < best) best = remaining;
});
elETA.textContent = (best !== null) ? formatETA(best) : '—';
var badge = root.querySelector('[data-badge]');
if(badge){
badge.textContent = ready>0 ? 'урожай готов' : ('следующий урожай: ' + (best !== null ? formatETA(best) : '—'));
}
}
function formatETA(sec){var s=Math.max(0,Math.round(sec));var m=Math.floor(s/60),r=s%60;return (m>0?(m+' мин '):'')+r+' сек';}
/* ===== Catalog UI ===== */
function buildCatalog(list){
elCat.innerHTML='';
list.forEach(function(item){
var card=document.createElement('button'); card.type='button'; card.className='seed';
if(item.img){ card.style.setProperty('--bg', cssUrl(item.img)); }
var body=document.createElement('div'); body.className='seed-body';
var nameEl = document.createElement('div'); nameEl.className='seed-name'; nameEl.textContent = item.name || 'Без названия';
var metaEl = document.createElement('div'); metaEl.className='seed-meta'; metaEl.textContent = Math.round((item.secs||150)/60) + ' мин';
body.appendChild(nameEl); body.appendChild(metaEl);
card.appendChild(body);
card.addEventListener('click', function(){
if(selected===null){ log('Выберите ячейку для посадки'); return; }
if(selected < 0 || selected >= CELLS){ log('Неверная ячейка'); return; }
var cell=gridState[selected];
if(cell.crop && pct(selected)<100){ log('Ячейка занята'); return; }
gridState[selected] = {
crop: item.key,
name: item.name,
secs: item.secs,
img: item.img,
plantedAt: Date.now(),
boostW: 0, boostL: 0, boostC: 0, water: 0
};
saveGrid(gridState); log('Посадка: '+item.name+' → яч. '+(selected+1)); buildGrid();
});
elCat.appendChild(card);
});
}
// Add via Semko URL
root.querySelector('[data-add-open]').addEventListener('click', function(){
var form=root.querySelector('[data-add-form]');
form.style.display=(form.style.display==='none'||form.style.display==='')?'flex':'none';
});
root.querySelector('[data-add-save]').addEventListener('click', function(){
var name=(root.querySelector('[data-add-name]').value||'').trim();
var img=(root.querySelector('[data-add-img]').value||'').trim();
var secs=parseInt(root.querySelector('[data-add-secs]').value,10)||150;
if(!name||!img){ alert('Укажи название и URL обложки с semco.ru'); return; }
// basic URL validation
try{ new URL(img); }catch(e){ alert('Неверный URL обложки'); return; }
var key='semko_'+Date.now();
catalog.unshift({key,name,secs,img});
saveCatalog(catalog);
buildCatalog(catalog);
root.querySelector('[data-add-name]').value='';
root.querySelector('[data-add-img]').value='';
root.querySelector('[data-add-secs]').value='150';
root.querySelector('[data-add-form]').style.display='none';
log('Добавлена культура из Семко: '+name+' (сохранено)');
});
root.querySelector('[data-search]').addEventListener('input', function(){
var q=this.value.toLowerCase().trim();
if(!q){ buildCatalog(catalog); return; }
var f=catalog.filter(c=> (c.name||'').toLowerCase().includes(q) );
buildCatalog(f);
});
/* ===== Grid UI ===== */
function buildGrid(){
elGrid.innerHTML='';
for(var i=0;i
';
ring.appendChild(rin);
var meta=document.createElement('div'); meta.className='kpis';
var k1=document.createElement('span'); k1.className='kpi'; k1.textContent = c.crop ? ('Рост: ' + Math.floor(pct(idx)) + '%') : 'Свободно';
var k2=document.createElement('span'); k2.className='kpi'; k2.textContent = boosts(c);
meta.appendChild(k1); meta.appendChild(k2);
mid.appendChild(ring); mid.appendChild(meta);
wrap.appendChild(top); wrap.appendChild(mid);
wrap.addEventListener('click', function(){ selected=idx; buildGrid(); });
elGrid.appendChild(wrap);
})(i);
}
updateStats();
}
/* ===== Actions (save after each) ===== */
function actWater(){ if(selected===null){ log('Выберите ячейку'); return; } var c=gridState[selected]; if(!c || !c.crop){ log('Ячейка пуста'); return; } c.boostW=Date.now()+60*1000; c.water=(c.water||0)+1; saveGrid(gridState); log('Полив яч. '+(selected+1)); buildGrid(); }
function actLight(){ if(selected===null){ log('Выберите ячейку'); return; } var c=gridState[selected]; if(!c || !c.crop){ log('Ячейка пуста'); return; } c.boostL=Date.now()+60*1000; saveGrid(gridState); log('Свет яч. '+(selected+1)); buildGrid(); }
function actClean(){ if(selected===null){ log('Выберите ячейку'); return; } var c=gridState[selected]; if(!c || !c.crop){ log('Ячейка пуста'); return; } c.boostC=Date.now()+30*1000; saveGrid(gridState); log('Уборка яч. '+(selected+1)); buildGrid(); }
function harvestOne(){ if(selected===null){ log('Выберите ячейку'); return; } var c=gridState[selected]; if(!(c && c.crop && pct(selected)>=100)) {log('Ещё не готово'); return;} log('Сбор: '+c.name+' (яч. '+(selected+1)+')'); gridState[selected]={crop:null,name:null,secs:150,img:null,plantedAt:null,boostW:0,boostL:0,boostC:0,water:0}; saveGrid(gridState); buildGrid(); }
function harvestAll(){ var cnt=0; gridState.forEach(function(c,i){ if(c && c.crop && pct(i)>=100){ cnt++; gridState[i]={crop:null,name:null,secs:150,img:null,plantedAt:null,boostW:0,boostL:0,boostC:0,water:0}; } }); if(cnt) saveGrid(gridState); log(cnt?('Собрано '+cnt+' шт.'): 'Готового нет'); buildGrid(); }
root.querySelector('[data-act="water"]').addEventListener('click', actWater);
root.querySelector('[data-act="light"]').addEventListener('click', actLight);
root.querySelector('[data-act="clean"]').addEventListener('click', actClean);
root.querySelector('[data-act="harvest-one"]').addEventListener('click', harvestOne);
root.querySelector('[data-act="harvest-all"]').addEventListener('click', harvestAll);
/* ===== Delivery / Gift ===== */
root.querySelector('[data-deliver]').addEventListener('click', function(){
var filled=gridState.filter(c=>c.crop).length;
if(!filled){ log('Грядка пуста'); return; }
log('Заявка на доставку: предметов '+filled);
// fetch('/api/deliver', {...}) — подключается по необходимости
});
var giftForm=root.querySelector('[data-gift-form]');
root.querySelector('[data-gift-open]').addEventListener('click', function(){
giftForm.style.display=(giftForm.style.display==='none'||giftForm.style.display==='')?'flex':'none';
});
root.querySelector('[data-gift-send]').addEventListener('click', function(){
var to=(root.querySelector('[data-gift-to]').value||'').trim();
var msg=(root.querySelector('[data-gift-msg]').value||'').trim();
if(!to){ alert('Укажи @username или телефон'); return; }
var count=gridState.filter(c=>c.crop).length;
if(!count){ log('Нечего дарить — грядка пуста'); return; }
// Для логирования безопасности: не вставляем msg в innerHTML нигде — только textContent через log
log('Подарок отправлен: '+to + (msg?(' • "'+msg+'"'):'') + ' • предметов: '+count);
// fetch('/api/gift', {...})
});
/* ===== Live tick ===== */
setInterval(function(){
// быстрый апдейт прогресса (без полного рендера)
var cells=elGrid.querySelectorAll('.cell');
cells.forEach(function(el){
var idxAttr = el.getAttribute('data-idx');
var idx = parseInt(idxAttr,10);
if(isNaN(idx)) return;
var ring=el.querySelector('.ring'); if(ring) ring.style.background=ringBG(pct(idx));
var st=el.querySelector('.cell-top span:last-child'); if(st) st.textContent = (gridState[idx] && gridState[idx].crop) ? stage(idx) : '+';
var meta=el.querySelector('.kpis'); if(meta){
// обновляем только текстовые KPI
var k1 = meta.querySelector('.kpi:first-child');
var k2 = meta.querySelector('.kpi:last-child');
if(k1) k1.textContent = (gridState[idx] && gridState[idx].crop) ? ('Рост: ' + Math.floor(pct(idx)) + '%') : 'Свободно';
if(k2) k2.textContent = boosts(gridState[idx]);
}
});
updateStats();
}, 1000);
/* ===== Init ===== */
buildCatalog(catalog);
buildGrid();
window.addEventListener('beforeunload', function(){ saveGrid(gridState); saveCatalog(catalog); });
})();