446 lines
20 KiB
JavaScript
446 lines
20 KiB
JavaScript
(function(){
|
||
"use strict";
|
||
var CUR = window.CURRICULUM || [];
|
||
var N = CUR.length;
|
||
var SECTIONS = [
|
||
{id:"words", icon:"📖", label:"Слова"},
|
||
{id:"dialogue", icon:"💬", label:"Диалог"},
|
||
{id:"reading", icon:"📰", label:"Чтение"},
|
||
{id:"listening", icon:"🎧", label:"Аудио"},
|
||
{id:"speaking", icon:"🎤", label:"Говорю"},
|
||
{id:"writing", icon:"✍️", label:"Пишу"}
|
||
];
|
||
var ACTIVE = "words";
|
||
var curIdx = 0;
|
||
var todayContentIdx = 0;
|
||
var dayKey = 0;
|
||
var state = null;
|
||
|
||
/* ---------- storage ---------- */
|
||
function todayStr(){ var d=new Date(); return d.getFullYear()+"-"+String(d.getMonth()+1).padStart(2,"0")+"-"+String(d.getDate()).padStart(2,"0"); }
|
||
function dateStr(d){ return d.getFullYear()+"-"+String(d.getMonth()+1).padStart(2,"0")+"-"+String(d.getDate()).padStart(2,"0"); }
|
||
function load(){
|
||
try{ state = JSON.parse(localStorage.getItem("enTrainer")||"null"); }catch(e){ state=null; }
|
||
var today = todayStr();
|
||
if(!state){ state={startDate:today, streak:1, lastVisit:today, completed:{}, wordsLearned:[]}; }
|
||
// streak
|
||
if(state.lastVisit !== today){
|
||
var y=new Date(); y.setDate(y.getDate()-1);
|
||
state.streak = (state.lastVisit===dateStr(y)) ? state.streak+1 : 1;
|
||
state.lastVisit = today;
|
||
}
|
||
if(!state.completed) state.completed={};
|
||
if(!state.completed[today]) state.completed[today]=[];
|
||
if(!state.wordsLearned) state.wordsLearned=[];
|
||
save();
|
||
}
|
||
function save(){ localStorage.setItem("enTrainer", JSON.stringify(state)); }
|
||
function completedToday(){ return state.completed[todayStr()] || (state.completed[todayStr()]=[]); }
|
||
function isDone(id){ return completedToday().indexOf(id)>=0; }
|
||
function setDone(id, on){
|
||
var c=completedToday();
|
||
var i=c.indexOf(id);
|
||
if(on && i<0){ c.push(id); }
|
||
if(!on && i>=0){ c.splice(i,1); }
|
||
save();
|
||
}
|
||
|
||
/* ---------- day math ---------- */
|
||
function computeDay(){
|
||
var s=new Date(state.startDate+"T00:00:00");
|
||
var t=new Date(); t.setHours(0,0,0,0);
|
||
dayKey = Math.max(0, Math.round((t-s)/86400000));
|
||
todayContentIdx = ((dayKey % N)+N)%N;
|
||
curIdx = todayContentIdx;
|
||
}
|
||
function day(){ return CUR[curIdx]; }
|
||
|
||
/* ---------- speech ---------- */
|
||
var voices=[];
|
||
var synth = window.speechSynthesis;
|
||
function loadVoices(){ if(synth) voices = synth.getVoices(); }
|
||
if(synth){ loadVoices(); synth.onvoiceschanged=loadVoices; }
|
||
function enVoice(pref){
|
||
var en = voices.filter(function(v){ return /en(-|_)?US/i.test(v.lang) || /en(-|_)?GB/i.test(v.lang); });
|
||
if(!en.length) en = voices.filter(function(v){ return /^en/i.test(v.lang); });
|
||
if(pref) for(var i=0;i<en.length;i++){ if(new RegExp(pref,"i").test(en[i].name)) return en[i]; }
|
||
return en[0]||null;
|
||
}
|
||
function speak(text, opts){
|
||
opts=opts||{};
|
||
if(!synth) return Promise.resolve();
|
||
return new Promise(function(res){
|
||
var u=new SpeechSynthesisUtterance(text);
|
||
u.lang="en-US";
|
||
var v=enVoice(opts.voice);
|
||
if(v) u.voice=v;
|
||
u.rate=opts.rate||1; u.pitch=opts.pitch||1;
|
||
u.onend=res; u.onerror=res;
|
||
synth.speak(u);
|
||
});
|
||
}
|
||
function stopSpeak(){ if(synth) synth.cancel(); }
|
||
|
||
/* ---------- recognition ---------- */
|
||
var REC = window.SpeechRecognition || window.webkitSpeechRecognition || null;
|
||
var currentRec=null;
|
||
function norm(s){ return (s||"").toLowerCase().replace(/[^a-z0-9\s']/g," ").replace(/\s+/g," ").trim(); }
|
||
function similarity(spoken, target){
|
||
var a=norm(spoken).split(" ").filter(Boolean);
|
||
var b=norm(target).split(" ").filter(Boolean);
|
||
if(!b.length) return 0;
|
||
var set={}; b.forEach(function(w){ set[w]=1; });
|
||
var hit=0; a.forEach(function(w){ if(set[w]){ hit++; delete set[w]; } });
|
||
return hit/b.length;
|
||
}
|
||
function levelOf(sim){ return sim>=0.8?"good":sim>=0.5?"ok":"bad"; }
|
||
function stopRec(){ if(currentRec){ try{currentRec.stop();}catch(e){} currentRec=null; } }
|
||
function startRec(onResult, onEnd){
|
||
if(!REC){ toast("Микрофон не поддерживается в этом браузере 🙁 Попробуй Chrome"); return false; }
|
||
stopRec(); stopSpeak();
|
||
var r=new REC();
|
||
r.lang="en-US"; r.interimResults=false; r.maxAlternatives=3;
|
||
r.onresult=function(e){
|
||
var t=""; for(var i=0;i<e.results.length;i++){ t+=e.results[i][0].transcript; }
|
||
onResult(t);
|
||
};
|
||
r.onerror=function(){ onEnd&&onEnd(); };
|
||
r.onend=function(){ onEnd&&onEnd(); };
|
||
currentRec=r;
|
||
try{ r.start(); return true; }catch(e){ onEnd&&onEnd(); return false; }
|
||
}
|
||
|
||
/* ---------- utils ---------- */
|
||
function $(id){ return document.getElementById(id); }
|
||
function esc(s){ return (s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); }
|
||
var toastT;
|
||
function toast(msg){
|
||
var t=$("toast"); t.textContent=msg; t.classList.add("show");
|
||
clearTimeout(toastT); toastT=setTimeout(function(){ t.classList.remove("show"); },2200);
|
||
}
|
||
|
||
/* ---------- render: header + dashboard ---------- */
|
||
function renderTop(){
|
||
$("streak").innerHTML="🔥 "+state.streak;
|
||
$("level").textContent="Intermediate";
|
||
}
|
||
function renderDashboard(){
|
||
var elapsed = dayKey+1;
|
||
var total=180;
|
||
var pct = Math.min(100, Math.round(elapsed/total*100));
|
||
var left = Math.max(0, total-elapsed);
|
||
$("goalTitle").textContent = "Цель: свободный английский за 6 месяцев";
|
||
$("goalSub").textContent = "Ты на пути! Каждый день — новый шаг к свободной речи с коллегами.";
|
||
$("goalBar").style.width = pct+"%";
|
||
$("goalMetaL").innerHTML = "День <b>"+elapsed+"</b> из "+total;
|
||
$("goalMetaR").innerHTML = "Осталось <b>"+left+"</b> дн.";
|
||
// mission chips
|
||
var h="";
|
||
SECTIONS.forEach(function(s){
|
||
var d=isDone(s.id);
|
||
h+='<div class="mchip '+(d?"done":"")+'" data-sec="'+s.id+'"><span class="ic">'+s.icon+'</span>'+s.label+'</div>';
|
||
});
|
||
$("mission").innerHTML=h;
|
||
$("mission").querySelectorAll(".mchip").forEach(function(el){
|
||
el.addEventListener("click",function(){ switchTab(el.getAttribute("data-sec")); window.scrollTo({top:0,behavior:"smooth"}); });
|
||
});
|
||
}
|
||
|
||
/* ---------- render: tabs ---------- */
|
||
function renderTabs(){
|
||
var h="";
|
||
SECTIONS.forEach(function(s){
|
||
h+='<button class="tab '+(ACTIVE===s.id?"active":"")+'" data-sec="'+s.id+'"><span class="dot"></span>'+s.icon+" "+s.label+'</button>';
|
||
});
|
||
$("tabrow").innerHTML=h;
|
||
$("tabrow").querySelectorAll(".tab").forEach(function(el){
|
||
el.addEventListener("click",function(){ switchTab(el.getAttribute("data-sec")); });
|
||
});
|
||
}
|
||
function switchTab(id){
|
||
stopSpeak(); stopRec();
|
||
ACTIVE=id;
|
||
document.querySelectorAll(".tab").forEach(function(el){ el.classList.toggle("active", el.getAttribute("data-sec")===id); });
|
||
document.querySelectorAll(".panel").forEach(function(p){ p.classList.remove("active"); });
|
||
$(id).classList.add("active");
|
||
renderPanel(id);
|
||
renderDoneBar();
|
||
}
|
||
|
||
/* ---------- day bar ---------- */
|
||
function renderDayBar(){
|
||
$("dayTag").innerHTML = 'День <b>'+(curIdx+1)+'</b> / '+N+(curIdx===todayContentIdx?' · сегодня':' · повторение');
|
||
}
|
||
function bindDayNav(){
|
||
$("prevDay").onclick=function(){ curIdx=(curIdx-1+N)%N; rerender(); };
|
||
$("nextDay").onclick=function(){ curIdx=(curIdx+1)%N; rerender(); };
|
||
}
|
||
|
||
/* ---------- panel router ---------- */
|
||
function renderPanel(id){
|
||
var d=day();
|
||
if(id==="words") renderWords(d);
|
||
if(id==="dialogue") renderDialogue(d);
|
||
if(id==="reading") renderReading(d);
|
||
if(id==="listening") renderListening(d);
|
||
if(id==="speaking") renderSpeaking(d);
|
||
if(id==="writing") renderWriting(d);
|
||
}
|
||
function renderTopicCard(d){
|
||
return '<div class="topic-card"><div class="em">'+d.emoji+'</div><div><div class="t">'+esc(d.topic)+'</div><div class="s">Тема дня · Intermediate</div></div></div>';
|
||
}
|
||
|
||
/* ---------- words ---------- */
|
||
function renderWords(d){
|
||
var h=renderTopicCard(d);
|
||
h+='<div class="card"><h3>📖 Новые слова ('+d.words.length+')</h3><div class="hint">Нажми на карточку, чтобы увидеть значение. ▶ — послушать произношение.</div>';
|
||
d.words.forEach(function(w,i){
|
||
var learned = state.wordsLearned.indexOf(w.en)>=0;
|
||
h+='<div class="word" data-i="'+i+'"><div class="word-top"><div><div class="word-en">'+esc(w.en)+'</div><div class="word-ru">'+esc(w.ru)+'</div></div><button class="play" data-play="'+i+'">▶</button></div><div class="word-def">'+esc(w.def)+'</div><div class="word-ex">"'+esc(w.ex)+'"</div><div class="stars">'+(learned?"⭐ Изучено":"")+'</div></div>';
|
||
});
|
||
h+='<div class="btnrow" style="margin-top:14px"><button class="btn ghost" id="playAll">▶ Все слова</button></div>';
|
||
h+='</div>';
|
||
$("words").innerHTML=h;
|
||
$("words").querySelectorAll(".word").forEach(function(el){
|
||
el.addEventListener("click",function(e){
|
||
if(e.target.closest(".play")) return;
|
||
el.classList.toggle("open");
|
||
});
|
||
});
|
||
$("words").querySelectorAll(".play").forEach(function(el){
|
||
el.addEventListener("click",function(e){
|
||
e.stopPropagation();
|
||
var i=+el.getAttribute("data-play");
|
||
var w=d.words[i];
|
||
stopSpeak(); speak(w.en);
|
||
if(state.wordsLearned.indexOf(w.en)<0){ state.wordsLearned.push(w.en); save(); }
|
||
var card=el.closest(".word"); card.querySelector(".stars").textContent="⭐ Изучено";
|
||
});
|
||
});
|
||
$("playAll").onclick=function(){
|
||
stopSpeak();
|
||
var p=Promise.resolve();
|
||
d.words.forEach(function(w){ p=p.then(function(){ return speak(w.en,{rate:0.95}).then(function(){ return new Promise(function(r){ setTimeout(r,250); }); }); }); });
|
||
};
|
||
}
|
||
|
||
/* ---------- dialogue ---------- */
|
||
function renderDialogue(d){
|
||
var h=renderTopicCard(d);
|
||
h+='<div class="card"><h3>💬 Диалог дня</h3><div class="hint">▶ — проиграть. Нажми строку, чтобы увидеть перевод.</div>';
|
||
h+='<div class="btnrow" style="margin-bottom:14px"><button class="btn primary" id="playAllDlg">▶ Весь диалог</button><button class="btn ghost" id="toggleTr">Перевод</button></div>';
|
||
h+='<div class="lines" id="lines">';
|
||
d.dialogue.forEach(function(l,i){
|
||
h+='<div class="line '+l.who+'" data-i="'+i+'"><div class="bubble"><div class="who">'+esc(l.name)+'</div>'+esc(l.text)+'<div class="tr">'+esc(l.tr)+'</div></div></div>';
|
||
});
|
||
h+='</div>';
|
||
h+='<div class="tips">Совет: повторяй реплики вслух за диктором — это тренирует произношение и скорость речи.</div>';
|
||
h+='</div>';
|
||
$("dialogue").innerHTML=h;
|
||
$("lines").querySelectorAll(".line").forEach(function(el){
|
||
el.addEventListener("click",function(){ el.classList.toggle("show-tr"); });
|
||
});
|
||
$("toggleTr").onclick=function(){
|
||
var all=$("lines").querySelectorAll(".line");
|
||
var anyOff=false; all.forEach(function(el){ if(!el.classList.contains("show-tr")) anyOff=true; });
|
||
all.forEach(function(el){ el.classList.toggle("show-tr", anyOff); });
|
||
};
|
||
$("playAllDlg").onclick=function(){
|
||
stopSpeak(); stopRec();
|
||
var p=Promise.resolve();
|
||
d.dialogue.forEach(function(l){
|
||
p=p.then(function(){
|
||
return speak(l.text,{rate:0.95, pitch: l.who==="A"?1:1.15, voice: l.who==="A"?"Male":"Female"});
|
||
}).then(function(){ return new Promise(function(r){ setTimeout(r,350); }); });
|
||
});
|
||
};
|
||
}
|
||
|
||
/* ---------- reading ---------- */
|
||
function renderReading(d){
|
||
var r=d.reading;
|
||
var h=renderTopicCard(d);
|
||
h+='<div class="card"><h3>📰 '+esc(r.title)+'</h3><div class="hint">Прочитай текст и ответь на вопросы.</div>';
|
||
h+='<p style="margin-bottom:16px;line-height:1.7">'+esc(r.text)+'</p>';
|
||
r.questions.forEach(function(qi,idx){
|
||
h+='<div class="q" data-q="'+idx+'"><div class="qt">'+(idx+1)+'. '+esc(qi.q)+'</div><div class="opts">';
|
||
qi.options.forEach(function(op,oi){
|
||
h+='<button class="opt" data-o="'+oi+'">'+esc(op)+'</button>';
|
||
});
|
||
h+='</div><div class="explain">'+esc(qi.ex)+'</div></div>';
|
||
});
|
||
h+='<div class="score" id="rScore"></div></div>';
|
||
$("reading").innerHTML=h;
|
||
bindQuiz("reading", r.questions);
|
||
}
|
||
|
||
/* ---------- listening ---------- */
|
||
function renderListening(d){
|
||
var l=d.listening;
|
||
var h=renderTopicCard(d);
|
||
h+='<div class="card"><h3>🎧 Аудирование</h3><div class="hint">Послушай аудио и ответь на вопросы. Можно менять скорость.</div>';
|
||
h+='<div class="audio-controls"><button class="btn primary" id="playL">▶ Слушать</button><div class="speed"><button data-sp="0.7">0.7×</button><button class="on" data-sp="1">1×</button><button data-sp="1.2">1.2×</button></div></div>';
|
||
h+='<button class="btn outline" id="showText" style="margin-bottom:14px">Показать текст</button>';
|
||
h+='<div class="listen-text" id="lText">'+esc(l.text)+'</div>';
|
||
l.questions.forEach(function(qi,idx){
|
||
h+='<div class="q" data-q="'+idx+'"><div class="qt">'+(idx+1)+'. '+esc(qi.q)+'</div><div class="opts">';
|
||
qi.options.forEach(function(op,oi){
|
||
h+='<button class="opt" data-o="'+oi+'">'+esc(op)+'</button>';
|
||
});
|
||
h+='</div><div class="explain">'+esc(qi.ex)+'</div></div>';
|
||
});
|
||
h+='<div class="score" id="lScore"></div></div>';
|
||
$("listening").innerHTML=h;
|
||
var rate=1;
|
||
$("playL").onclick=function(){ stopSpeak(); speak(l.text,{rate:rate}); };
|
||
$("listening").querySelectorAll(".speed button").forEach(function(b){
|
||
b.onclick=function(){
|
||
rate=parseFloat(b.getAttribute("data-sp"));
|
||
$("listening").querySelectorAll(".speed button").forEach(function(x){ x.classList.remove("on"); });
|
||
b.classList.add("on");
|
||
};
|
||
});
|
||
$("showText").onclick=function(){ $("lText").classList.toggle("show"); this.textContent=$("lText").classList.contains("show")?"Скрыть текст":"Показать текст"; };
|
||
bindQuiz("listening", l.questions);
|
||
}
|
||
|
||
/* ---------- quiz shared ---------- */
|
||
function bindQuiz(panelId, questions){
|
||
var root=$(panelId);
|
||
root.querySelectorAll(".q").forEach(function(qEl, qIdx){
|
||
var answered=false;
|
||
qEl.querySelectorAll(".opt").forEach(function(oEl){
|
||
oEl.addEventListener("click",function(){
|
||
if(answered) return; answered=true;
|
||
var o=+oEl.getAttribute("data-o");
|
||
var correct=questions[qIdx].answer;
|
||
qEl.querySelectorAll(".opt").forEach(function(x){ x.classList.add("disabled"); });
|
||
if(o===correct){ oEl.classList.add("correct"); }
|
||
else{ oEl.classList.add("wrong"); var c=qEl.querySelector('.opt[data-o="'+correct+'"]'); if(c) c.classList.add("correct"); }
|
||
qEl.querySelector(".explain").classList.add("show");
|
||
updateScore(root, questions.length, panelId);
|
||
});
|
||
});
|
||
});
|
||
}
|
||
function updateScore(root, total, panelId){
|
||
var got=0;
|
||
root.querySelectorAll(".q").forEach(function(qEl){
|
||
if(qEl.querySelector(".opt.correct") && !qEl.querySelector(".opt.wrong")) got++;
|
||
});
|
||
var sc=$(panelId==="reading"?"rScore":"lScore");
|
||
if(sc){
|
||
sc.textContent="Результат: "+got+" / "+total+(got===total?" 🎉 Отлично!":"");
|
||
sc.classList.add("show");
|
||
}
|
||
}
|
||
|
||
/* ---------- speaking ---------- */
|
||
function renderSpeaking(d){
|
||
var s=d.speaking;
|
||
var sup = !!REC;
|
||
var h=renderTopicCard(d);
|
||
h+='<div class="card"><h3>🎤 Практика говорения</h3><div class="hint">Прочитай задание и ответь вслух. Я распознаю речь и сравню с образцом.</div>';
|
||
h+='<div class="tips" style="margin-bottom:14px"><b>Задание:</b> '+esc(s.prompt)+'</div>';
|
||
h+='<div class="speak-box">';
|
||
if(sup){
|
||
h+='<button class="mic" id="mic">🎤</button>';
|
||
h+='<div id="micState">Нажми на микрофон и говори</div>';
|
||
} else {
|
||
h+='<div style="font-size:40px">🎤</div>';
|
||
h+='<div class="rec-badge">Микрофон недоступен в этом браузере</div>';
|
||
h+='<div style="font-size:13px;color:var(--gray-500)">Открой в Chrome на телефоне или компьютере, чтобы практиковать речь.</div>';
|
||
}
|
||
h+='<div class="transcript" id="tr"><span class="ph">Твой ответ появится здесь…</span></div>';
|
||
h+='<div class="btnrow"><button class="btn ghost" id="hearModel">▶ Образец</button><button class="btn outline" id="showTarget">Показать ответ</button></div>';
|
||
h+='<div class="target" id="target"><b>Образцовый ответ:</b>'+esc(s.target)+'</div>';
|
||
h+='</div>';
|
||
h+='<div class="tips">💡 Не бойся ошибок — главное пробовать говорить вслух каждый день. Это ключ к свободной речи.</div>';
|
||
h+='</div>';
|
||
$("speaking").innerHTML=h;
|
||
$("hearModel").onclick=function(){ stopSpeak(); speak(s.target,{rate:0.9}); };
|
||
$("showTarget").onclick=function(){ $("target").classList.toggle("show"); };
|
||
if(sup){
|
||
var mic=$("mic"), state2=$("micState"), tr=$("tr");
|
||
mic.onclick=function(){
|
||
if(mic.classList.contains("rec")){ stopRec(); return; }
|
||
var ok=startRec(function(t){
|
||
tr.innerHTML=esc(t);
|
||
var sim=similarity(t, s.target);
|
||
var lv=levelOf(sim);
|
||
var label = lv==="good"?"Отлично! 🎉":lv==="ok"?"Неплохо, почти! 👍":"Попробуй ещё раз 💪";
|
||
var tr2=document.createElement("div"); tr2.className="match "+lv; tr2.textContent=label+" ("+Math.round(sim*100)+"% совпадение)";
|
||
tr.appendChild(tr2);
|
||
},function(){
|
||
mic.classList.remove("rec"); mic.textContent="🎤"; state2.textContent="Нажми на микрофон и говори";
|
||
});
|
||
if(ok){ mic.classList.add("rec"); mic.textContent="⏹"; state2.innerHTML='<span class="rec-badge"><span class="blink"></span>Слушаю…</span>'; }
|
||
};
|
||
}
|
||
}
|
||
|
||
/* ---------- writing ---------- */
|
||
function renderWriting(d){
|
||
var w=d.writing;
|
||
var key="w_"+todayStr()+"_"+curIdx;
|
||
var saved=localStorage.getItem(key)||"";
|
||
var h=renderTopicCard(d);
|
||
h+='<div class="card"><h3>✍️ Практика письма</h3><div class="hint">Напиши ответ и сравни с образцом.</div>';
|
||
h+='<div class="tips" style="margin-bottom:14px"><b>Задание:</b> '+esc(w.prompt)+'</div>';
|
||
h+='<textarea id="ta" placeholder="Пиши здесь на английском…">'+esc(saved)+'</textarea>';
|
||
h+='<div class="btnrow" style="margin-top:10px"><button class="btn primary" id="saveW">Сохранить</button><button class="btn ghost" id="showModel">Показать образец</button></div>';
|
||
h+='<div class="model" id="model"><b>Образцовый ответ:</b>'+esc(w.model)+'</div>';
|
||
h+='<div class="tips" style="margin-top:12px">💡 '+esc(w.tips)+'</div>';
|
||
h+='</div>';
|
||
$("writing").innerHTML=h;
|
||
$("saveW").onclick=function(){ localStorage.setItem(key, $("ta").value); toast("Сохранено ✅"); };
|
||
$("showModel").onclick=function(){ $("model").classList.toggle("show"); };
|
||
}
|
||
|
||
/* ---------- done bar ---------- */
|
||
function renderDoneBar(){
|
||
var sec=SECTIONS.filter(function(s){return s.id===ACTIVE;})[0];
|
||
var done=isDone(ACTIVE);
|
||
var c=completedToday();
|
||
$("doneInfo").innerHTML = 'Сегодня выполнено: <b>'+c.length+"/"+SECTIONS.length+'</b> · '+sec.label;
|
||
var b=$("doneBtn");
|
||
b.textContent=done?"✓ Выполнено":"Отметить выполненным";
|
||
b.classList.toggle("done", done);
|
||
}
|
||
function bindDoneBar(){
|
||
$("doneBtn").onclick=function(){
|
||
var on=!isDone(ACTIVE);
|
||
setDone(ACTIVE, on);
|
||
renderDoneBar();
|
||
renderDashboard();
|
||
if(on){
|
||
var c=completedToday();
|
||
if(c.length===SECTIONS.length) toast("Все задания дня готовы! Так держать 🚀");
|
||
else toast("Готово! +1 к прогрессу 💪");
|
||
}
|
||
};
|
||
}
|
||
|
||
/* ---------- rerender ---------- */
|
||
function rerender(){
|
||
renderDayBar();
|
||
renderPanel(ACTIVE);
|
||
renderDoneBar();
|
||
}
|
||
|
||
/* ---------- init ---------- */
|
||
function init(){
|
||
load();
|
||
computeDay();
|
||
renderTop();
|
||
renderDashboard();
|
||
renderTabs();
|
||
renderDayBar();
|
||
bindDayNav();
|
||
bindDoneBar();
|
||
switchTab("words");
|
||
}
|
||
if(document.readyState!=="loading") init();
|
||
else document.addEventListener("DOMContentLoaded", init);
|
||
})();
|