(function(){
function log(){ try { var args=[].slice.call(arguments); args.unshift('[KEE]'); console.log.apply(console,args);} catch(e){}}
function escapeHtml(str){ return String(str||'').replace(/[&<>"']/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]; });}
function shuffle(arr){ return arr.slice().sort(function(){ return Math.random() - 0.5; });}
function chunk(arr, size){ var out=[]; for(var i=0;i<arr.length;i+=size) out.push(arr.slice(i, i+size)); return out; }
function uniqueBy(arr, fn){ var seen={}; return arr.filter(function(item){ var k=fn(item); if(seen[k]) return false; seen[k]=1; return true; });}
function getInlineData(app){
try {
var appId=app.getAttribute('data-kee-app-id');
var node=document.querySelector('.kee-inline-data[data-for-app="' + appId + '"]');
if(node) return JSON.parse(node.textContent||'{}');
} catch(err){ log('inline data parse error', err); }
return null;
}
function initApp(app){
var data=getInlineData(app)||window.keeData||{sections:[],strings:{},settings:{}};
if(!data.sections||!data.sections.length) return;
var tabsEl=app.querySelector('[data-kee-tabs]');
var panelEl=app.querySelector('[data-kee-section-panel]');
var selectEl=app.querySelector('[data-kee-section-select]');
var startBtn=app.querySelector('[data-kee-start-button]');
var startBtnText=startBtn ? startBtn.querySelector('.kee-btn-start-text'):null;
var progressBar=app.querySelector('[data-kee-progress-bar]');
var progressText=app.querySelector('[data-kee-progress-text]');
var progressComplete=app.querySelector('[data-kee-progress-complete]');
var progressResetWrap=app.querySelector('[data-kee-progress-reset-wrap]');
var progressResetBtn=app.querySelector('[data-kee-progress-reset]');
var sections=data.sections.slice();
var activeIndex=0;
var selectedSectionId=String(sections[0].id);
var hasStarted=false;
var learnedKey='kee_learned_items_v2';
var learned=getStorage(learnedKey, []);
var completionStorageKey='kee_progress_complete_v1';
var completionCelebrated=getStorage(completionStorageKey, false)===true&&learned.length > 0;
function getStorage(key, fallback){ try{ var raw=localStorage.getItem(key); return raw ? JSON.parse(raw):fallback; }catch(e){ return fallback; }}
function setStorage(key, value){ try{ localStorage.setItem(key, JSON.stringify(value)); }catch(e){}}
function currentSection(){ return sections[activeIndex]||null; }
function syncSelect(){ var section=currentSection(); if(section&&selectEl){ selectEl.value=String(section.id); selectedSectionId=String(section.id);} updateStartButton(); }
function updateStartButton(){ if(!startBtn) return; var section=sections.filter(function(s){ return String(s.id)===String(selectedSectionId); })[0]; var label=section ? ('Open ' + section.title):(data.strings.startSection||'Open Section'); if(startBtnText) startBtnText.textContent=label; startBtn.setAttribute('aria-label', label); }
function playCelebration(){
try {
var Ctx=window.AudioContext||window.webkitAudioContext;
if(!Ctx) return;
var ctx=new Ctx();
var notes=[523.25, 659.25, 783.99, 1046.5];
notes.forEach(function(freq, index){
var osc=ctx.createOscillator();
var gain=ctx.createGain();
osc.connect(gain); gain.connect(ctx.destination);
osc.type='triangle';
osc.frequency.value=freq;
var start=ctx.currentTime + (index * 0.12);
gain.gain.setValueAtTime(0.0001, start);
gain.gain.linearRampToValueAtTime(0.14, start + 0.02);
gain.gain.exponentialRampToValueAtTime(0.0001, start + 0.24);
osc.start(start);
osc.stop(start + 0.24);
});
} catch(e){}}
function updateProgress(){
var total=sections.reduce(function(sum,s){ return sum + ((s.items||[]).length); },0)||1;
var pct=Math.min(100, Math.round((learned.length/total)*100));
var isComplete=pct >=100&&learned.length >=total;
if(progressBar) progressBar.style.width=pct + '%';
if(progressText) progressText.textContent=pct + '%';
if(progressComplete) progressComplete.classList.toggle('kee-hide', !isComplete);
if(progressResetWrap) progressResetWrap.classList.toggle('kee-hide', !isComplete);
if(isComplete&&!completionCelebrated){
completionCelebrated=true;
setStorage(completionStorageKey, true);
playCelebration();
}
if(!isComplete){
completionCelebrated=false;
setStorage(completionStorageKey, false);
}}
function updateLearnedCountUI(){
try {
var section=currentSection();
if(!section||!panelEl) return;
var countNode=panelEl.querySelector('[data-kee-learned-count]');
if(countNode){
var count=(section.items||[]).filter(function(i){ return learned.indexOf(i.id)!==-1; }).length;
countNode.textContent=String(count);
}} catch(e){}}
function markLearned(id){
if(learned.indexOf(id)===-1){
learned.push(id);
setStorage(learnedKey, learned);
updateProgress();
updateLearnedCountUI();
}}
function resetProgress(){
learned=[];
setStorage(learnedKey, learned);
completionCelebrated=false;
setStorage(completionStorageKey, false);
updateProgress();
renderSection();
}
function cleanAlphabetTitle(title, section){ var value=String(title||''); if(section&&/alphabet/i.test(String(section.title||''))) value=value.replace(/^capital\s+/i,'').trim(); return value; }
function cleanConversationText(text){ return String(text||'').replace(/^[A-Z]:\s*/i,'').trim(); }
function getDisplayTitle(item, section){ var value=cleanAlphabetTitle(item&&item.title, section); if(item&&item.type==='conversation') value=cleanConversationText(value); return value; }
function getDisplayAlt(item){ var value=String(item&&item.alt||''); return item&&item.type==='conversation' ? cleanConversationText(value):value; }
function getDisplayExample(item){ var value=String(item&&item.example||''); return item&&item.type==='conversation' ? cleanConversationText(value):value; }
function getPrompt(item, section){ return [getDisplayExample(item), getDisplayAlt(item), item.pronunciation, getDisplayTitle(item, section)].filter(Boolean)[0]||''; }
function getSpeechText(item, section){ return [getDisplayTitle(item, section), getDisplayAlt(item), getDisplayExample(item)].filter(Boolean).join('. '); }
function speak(text, audioUrl){
if(audioUrl){ try{ new Audio(audioUrl).play(); return; } catch(e){}}
if('speechSynthesis' in window){
try{ var u=new SpeechSynthesisUtterance(text); u.lang='en-GB'; window.speechSynthesis.cancel(); window.speechSynthesis.speak(u); }catch(err){ log('speech error', err); }}
}
function beep(type){
try { var Ctx=window.AudioContext||window.webkitAudioContext; if(!Ctx) return; var ctx=new Ctx(); var osc=ctx.createOscillator(); var gain=ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.type='sine'; osc.frequency.value=type==='good' ? 660:220; gain.gain.setValueAtTime(0.12, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.2); osc.start(); osc.stop(ctx.currentTime+0.2);} catch(e){}}
function makeSpeakerButton(itemId, place, extra){
extra=extra||'';
return '<button class="kee-speak-btn ' + extra + '" type="button" data-action="speak" data-id="' + itemId + '" aria-label="Speak">🔊</button>';
}
function renderLearn(items){
var section=currentSection()||{};
return '<div class="kee-items-grid">' + items.map(function(item){
var learnedClass=learned.indexOf(item.id)!==-1 ? ' learned':'';
return '<article class="kee-item-card'+ learnedClass +'">' +
'<div class="kee-item-top"><div><div class="kee-item-title">' + escapeHtml(getDisplayTitle(item, section)) + '</div>' +
(item.pronunciation ? '<div class="kee-item-pron">' + escapeHtml(item.pronunciation) + '</div>':'') +
'</div><div class="kee-item-side">' + makeSpeakerButton(item.id,'card') + '<div class="kee-item-icon">' + escapeHtml(item.icon||'✨') + '</div></div></div>' +
(getDisplayAlt(item) ? '<div class="kee-item-alt">' + escapeHtml(getDisplayAlt(item)) + '</div>':'') +
(getDisplayExample(item) ? '<div class="kee-item-example">' + escapeHtml(getDisplayExample(item)) + '</div>':'') +
'<div class="kee-item-actions">' +
'<button class="kee-btn primary" type="button" data-action="listen" data-id="' + item.id + '">🔊 ' + escapeHtml(data.strings.listen||'Listen') + '</button>' +
'<button class="kee-btn soft" type="button" data-action="learned" data-id="' + item.id + '">⭐ Learned</button>' +
'</div></article>';
}).join('') + '</div>';
}
function renderQuickReview(items){
var section=currentSection()||{};
return '<div class="kee-mini-list">' + items.map(function(item){
return '<div class="kee-mini-item"><div class="emoji">' + escapeHtml(item.icon||'✨') + '</div><div class="kee-mini-copy"><strong>' + escapeHtml(getDisplayTitle(item, section)) + '</strong>' +
(item.pronunciation ? '<div class="kee-small">' + escapeHtml(item.pronunciation) + '</div>':'') +
(getDisplayExample(item) ? '<div class="kee-small">' + escapeHtml(getDisplayExample(item)) + '</div>':'') +
'</div>' + makeSpeakerButton(item.id, 'quick', 'kee-speak-inline') + '</div>';
}).join('') + '</div>';
}
function renderMatchShell(items){
var count=Math.min(3, items.length);
return '<div class="kee-match-shell" data-match-shell data-match-size="'+ count +'"></div>';
}
function renderQuizShell(items){
return '<div class="kee-quiz-box" data-quiz-box><button class="kee-btn primary" type="button" data-action="start-quiz">' + escapeHtml(data.strings.startQuiz||'Start Quiz') + '</button><div class="kee-game-status">Quiz uses this section\'s own words and phrases.</div></div>';
}
function renderTabs(){
tabsEl.innerHTML='';
sections.forEach(function(section, idx){
var btn=document.createElement('button');
btn.type='button';
btn.className='kee-tab' + (idx===activeIndex ? ' active':'');
btn.setAttribute('data-section-index', idx);
btn.style.background=idx===activeIndex ? section.color:'#fff';
btn.style.color=idx===activeIndex ? '#fff':'#111827';
btn.textContent=(section.icon ? section.icon + ' ':'') + section.title;
tabsEl.appendChild(btn);
});
}
function bindCommonActions(items){
var section=currentSection()||{};
panelEl.querySelectorAll('[data-action="listen"], [data-action="speak"]').forEach(function(btn){
btn.addEventListener('click', function(e){
e.preventDefault(); e.stopPropagation();
var id=Number(btn.getAttribute('data-id'));
var item=items.filter(function(i){ return Number(i.id)===id; })[0];
if(item) speak(getSpeechText(item, section), item.audio||'');
});
});
panelEl.querySelectorAll('[data-action="learned"]').forEach(function(btn){
btn.addEventListener('click', function(e){ e.preventDefault(); e.stopPropagation(); markLearned(Number(btn.getAttribute('data-id'))); });
});
}
function setupMatch(items){
var shell=panelEl.querySelector('[data-match-shell]');
if(!shell) return;
var section=currentSection()||{};
var pages=chunk(shuffle(items), 3);
var pageIndex=0;
var selectedClueId=null;
var locked=false;
var matchedThisPage=0;
var currentPageSize=0;
function clearSelection(){
selectedClueId=null;
shell.querySelectorAll('[data-drag-id]').forEach(function(n){ n.classList.remove('active'); });
}
function advanceIfFinished(){
if(locked||matchedThisPage < currentPageSize) return;
locked=true;
setTimeout(function(){
pageIndex +=1;
if(pageIndex < pages.length){
locked=false;
renderPage();
}else{
shell.innerHTML='<div class="kee-feedback-good">' + escapeHtml(data.strings.matchDone||'All matching questions were completed.') + '</div>';
}}, 700);
}
function selectClue(btn){
if(!btn||btn.classList.contains('matched')||locked) return;
selectedClueId=btn.getAttribute('data-drag-id');
shell.querySelectorAll('[data-drag-id]').forEach(function(n){ n.classList.remove('active'); });
btn.classList.add('active');
}
function handleDropOnTarget(target){
if(!selectedClueId||locked||target.classList.contains('correct')) return false;
var targetId=target.getAttribute('data-target-id');
var feedback=shell.querySelector('[data-match-feedback]');
var clueBtn=shell.querySelector('[data-drag-id="' + selectedClueId + '"]');
if(String(targetId)===String(selectedClueId)){
target.classList.remove('wrong','active-drop');
target.classList.add('correct');
target.setAttribute('aria-disabled', 'true');
if(clueBtn){
clueBtn.classList.remove('active');
clueBtn.classList.add('matched');
clueBtn.disabled=true;
clueBtn.setAttribute('draggable', 'false');
}
matchedThisPage +=1;
if(feedback) feedback.innerHTML='<div class="kee-feedback-good">' + escapeHtml(data.strings.correct||'Great job!') + '</div>';
beep('good');
markLearned(Number(targetId));
clearSelection();
advanceIfFinished();
}else{
target.classList.add('wrong');
if(feedback) feedback.innerHTML='<div class="kee-feedback-bad">' + escapeHtml(data.strings.wrong||'Try again!') + '</div>';
beep('bad');
clearSelection();
setTimeout(function(){ target.classList.remove('wrong'); }, 700);
}
return false;
}
function renderPage(){
if(!pages.length){ shell.innerHTML='<div class="kee-game-status">No items found.</div>'; return; }
var page=pages[pageIndex]||[];
currentPageSize=page.length;
matchedThisPage=0;
clearSelection();
var clues=shuffle(page.map(function(item){ return {id:item.id, label:getPrompt(item, section)};}));
shell.innerHTML='<div class="kee-match-head"><div class="kee-game-status">Drag a clue onto the correct card, or tap a clue and then tap its card.</div><div class="kee-small">Set ' + (pageIndex+1) + ' / ' + pages.length + '</div></div>' +
'<div class="kee-match-area"><div><h5>Words</h5><div class="kee-match-targets">' + page.map(function(item){
return '<div class="kee-target" data-target-id="'+ item.id +'" tabindex="0" role="button"><div class="kee-target-top"><strong>' + escapeHtml(item.icon||'✨') + ' ' + escapeHtml(getDisplayTitle(item, section)) + '</strong>' + makeSpeakerButton(item.id,'match-target','kee-speak-inline') + '</div><div class="kee-small">Drop the correct clue here, or tap after selecting a clue.</div></div>';
}).join('') + '</div></div><div><h5>Clues</h5><div class="kee-match-draggables">' + clues.map(function(clue){
return '<button class="kee-drag" type="button" draggable="true" data-drag-id="'+ clue.id +'">' + escapeHtml(clue.label) + '</button>';
}).join('') + '</div></div></div><div class="kee-match-feedback" data-match-feedback></div>';
shell.querySelectorAll('[data-drag-id]').forEach(function(btn){
btn.addEventListener('click', function(e){
e.preventDefault(); e.stopPropagation();
selectClue(btn);
});
btn.addEventListener('dragstart', function(e){
if(btn.classList.contains('matched')||locked){ e.preventDefault(); return; }
selectClue(btn);
try {
e.dataTransfer.setData('text/plain', String(btn.getAttribute('data-drag-id')));
e.dataTransfer.effectAllowed='move';
} catch(err){}});
btn.addEventListener('dragend', function(){
shell.querySelectorAll('[data-target-id]').forEach(function(target){ target.classList.remove('active-drop'); });
});
});
shell.querySelectorAll('[data-target-id]').forEach(function(target){
target.addEventListener('click', function(e){
e.preventDefault(); e.stopPropagation();
handleDropOnTarget(target);
});
target.addEventListener('dragover', function(e){
e.preventDefault();
if(!locked&&!target.classList.contains('correct')) target.classList.add('active-drop');
try { e.dataTransfer.dropEffect='move'; } catch(err){}});
target.addEventListener('dragenter', function(e){ e.preventDefault(); if(!locked&&!target.classList.contains('correct')) target.classList.add('active-drop'); });
target.addEventListener('dragleave', function(){ target.classList.remove('active-drop'); });
target.addEventListener('drop', function(e){
e.preventDefault(); e.stopPropagation();
target.classList.remove('active-drop');
try {
var transferred=e.dataTransfer.getData('text/plain');
if(transferred) selectedClueId=transferred;
} catch(err){}
handleDropOnTarget(target);
});
});
bindCommonActions(items);
}
renderPage();
}
function buildQuizSource(items, section){
var uniqueItems=shuffle(uniqueBy(items, function(i){ return getDisplayTitle(i, section).toLowerCase(); }));
if(uniqueItems.length <=5) return uniqueItems.slice();
return uniqueItems.slice(0, 5);
}
function setupQuiz(items){
var box=panelEl.querySelector('[data-quiz-box]');
if(!box) return;
var section=currentSection()||{};
var state=null;
function renderDone(){
if(!state) return;
box.innerHTML='<div class="kee-quiz-question">All questions were completed.</div><div class="kee-game-status">' + escapeHtml(data.strings.yourScore||'Your score') + ': ' + state.score + '/' + state.source.length + '</div><button class="kee-btn primary" type="button" data-action="start-quiz">Play again</button>';
}
function renderQuestion(){
if(!state) return;
if(state.round >=state.source.length){
renderDone();
return;
}
var current=state.source[state.round];
var distractors=uniqueBy(items.filter(function(i){
return i.id!==current.id&&getDisplayTitle(i, section).toLowerCase()!==getDisplayTitle(current, section).toLowerCase();
}), function(i){ return getDisplayTitle(i, section).toLowerCase(); });
var others=shuffle(distractors).slice(0, 3);
var options=shuffle([current].concat(others));
if(options.filter(function(o){ return Number(o.id)===Number(current.id); }).length===0){ options[0]=current; }
state.answered=false;
box.innerHTML='<div class="kee-quiz-question">Question ' + (state.round + 1) + ' of ' + state.source.length + ': Which word matches this clue?</div><div class="kee-game-status kee-quiz-prompt">' + makeSpeakerButton(current.id,'quiz','kee-speak-inline') + '<span>' + escapeHtml(getPrompt(current, section)) + '</span></div><div class="kee-quiz-options">' + options.map(function(opt){
return '<button type="button" data-option-id="' + opt.id + '">' + escapeHtml((opt.icon ? opt.icon + ' ':'') + getDisplayTitle(opt, section)) + '</button>';
}).join('') + '</div><div class="kee-quiz-feedback" data-quiz-feedback></div>';
bindCommonActions(items);
}
box.addEventListener('click', function(ev){
var startBtn=ev.target.closest('[data-action="start-quiz"]');
if(startBtn){
ev.preventDefault(); ev.stopPropagation();
state={ source: buildQuizSource(items, section), round: 0, score: 0, answered: false };
if(!state.source.length){
box.innerHTML='<div class="kee-game-status">No items found.</div>';
return false;
}
renderQuestion();
return false;
}
var optionBtn=ev.target.closest('[data-option-id]');
if(!optionBtn||!state||state.answered) return;
ev.preventDefault(); ev.stopPropagation();
state.answered=true;
box.querySelectorAll('[data-option-id]').forEach(function(node){ node.disabled=true; });
var current=state.source[state.round];
var chosen=Number(optionBtn.getAttribute('data-option-id'));
var feedback=box.querySelector('[data-quiz-feedback]');
var correctBtn=box.querySelector('[data-option-id="' + current.id + '"]');
if(chosen===Number(current.id)){
optionBtn.classList.add('correct');
state.score +=1;
if(feedback) feedback.innerHTML='<div class="kee-feedback-good">Correct!</div>';
beep('good');
markLearned(Number(current.id));
}else{
optionBtn.classList.add('wrong');
if(correctBtn) correctBtn.classList.add('correct');
if(feedback) feedback.innerHTML='<div class="kee-feedback-bad">Not quite. Correct answer: <strong>' + escapeHtml(getDisplayTitle(current, section)) + '</strong></div>';
beep('bad');
}
setTimeout(function(){
if(!state) return;
state.round +=1;
renderQuestion();
}, 900);
return false;
});
}
function renderSection(){
if(!hasStarted){
panelEl.innerHTML='<div class="kee-idle-card"><div class="kee-idle-emoji">🌈</div><h3>' + escapeHtml(data.strings.chooseAndStart||'Choose a section to begin') + '</h3><p>' + escapeHtml(data.strings.chooseAndStartHelp||'') + '</p></div>';
return;
}
var section=currentSection();
if(!section){ panelEl.innerHTML='<div class="kee-empty">No section found.</div>'; return; }
var items=Array.isArray(section.items) ? section.items:[];
var activeMode=panelEl.getAttribute('data-kee-active-mode')||'learn';
function applyMode(mode){
activeMode=mode||'learn';
panelEl.setAttribute('data-kee-active-mode', activeMode);
panelEl.querySelectorAll('[data-mode]').forEach(function(node){ node.classList.toggle('active', node.getAttribute('data-mode')===activeMode); });
var content=panelEl.querySelector('[data-mode-content]');
if(!content) return;
if(activeMode==='learn') content.innerHTML=items.length ? renderLearn(items):'<p>No items found.</p>';
if(activeMode==='match') content.innerHTML=renderMatchShell(items);
if(activeMode==='quiz') content.innerHTML=renderQuizShell(items);
bindCommonActions(items);
if(activeMode==='match') setupMatch(items);
if(activeMode==='quiz') setupQuiz(items);
}
panelEl.innerHTML='<section data-current-section-id="' + escapeHtml(section.id) + '">' +
'<div class="kee-section-header"><div><div class="kee-section-title"><span class="kee-icon">' + escapeHtml(section.icon||'📘') + '</span><div><h3>' + escapeHtml(section.title) + '</h3><p>' + escapeHtml(section.description||'') + '</p></div></div></div>' +
'<div class="kee-card-grid"><div class="kee-card"><strong>' + items.length + '</strong><div class="kee-small">Items in this section</div></div><div class="kee-card"><strong data-kee-learned-count>' + items.filter(function(i){ return learned.indexOf(i.id)!==-1; }).length + '</strong><div class="kee-small">Marked learned</div></div><div class="kee-card"><strong>' + Math.min(3, items.length) + '</strong><div class="kee-small">Matching cards per round</div></div></div></div>' +
'<div class="kee-mode-grid"><div class="kee-mode-card"><h4>Play & Practice</h4><div class="kee-mode-switch"><button class="' + (activeMode==='learn' ? 'active':'') + '" type="button" data-mode="learn">Learn Cards</button><button class="' + (activeMode==='match' ? 'active':'') + '" type="button" data-mode="match">Matching Game</button><button class="' + (activeMode==='quiz' ? 'active':'') + '" type="button" data-mode="quiz">Quiz</button></div><div data-mode-content></div></div><aside class="kee-game-card"><h4>Quick Review</h4>' + (items.length ? renderQuickReview(items):'<p>No items found.</p>') + '</aside></div></section>';
panelEl.querySelectorAll('[data-mode]').forEach(function(btn){
btn.addEventListener('click', function(e){
e.preventDefault(); e.stopPropagation();
applyMode(btn.getAttribute('data-mode'));
});
});
applyMode(activeMode);
}
function startSection(){
var idx=sections.findIndex(function(s){ return String(s.id)===String(selectedSectionId); });
activeIndex=idx >=0 ? idx:0;
hasStarted=true;
syncSelect();
renderTabs();
renderSection();
log('started section', selectedSectionId);
return false;
}
if(selectEl){
selectEl.addEventListener('change', function(){ selectedSectionId=String(selectEl.value||''); updateStartButton(); startSection(); });
}
if(startBtn){
startBtn.addEventListener('click', function(e){ e.preventDefault(); e.stopPropagation(); if(selectEl&&selectEl.value) selectedSectionId=String(selectEl.value); startSection(); });
}
if(tabsEl){
tabsEl.addEventListener('click', function(e){ var btn=e.target.closest('[data-section-index]'); if(!btn) return; e.preventDefault(); activeIndex=Number(btn.getAttribute('data-section-index'))||0; hasStarted=true; syncSelect(); renderTabs(); renderSection(); });
}
if(progressResetBtn){
progressResetBtn.addEventListener('click', function(e){
e.preventDefault(); e.stopPropagation();
resetProgress();
});
}
app._keeApi={ startSelectedSection: startSection, setSelectedSectionId: function(id){ selectedSectionId=String(id||''); updateStartButton(); }, resetProgress: resetProgress };
updateProgress(); syncSelect(); renderTabs(); renderSection();
}
function initAll(){ document.querySelectorAll('[data-kee-app]').forEach(function(app){ if(app.getAttribute('data-kee-initialized')==='1') return; app.setAttribute('data-kee-initialized','1'); initApp(app); });}
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded', initAll); else initAll();
window.addEventListener('load', initAll);
window.KEEStartSection=function(el){ try{ var app=el.closest('[data-kee-app]'); if(app&&app._keeApi) return app._keeApi.startSelectedSection(); }catch(e){ log('start fallback error', e); } return false; };
window.KEESelectAndStart=function(el){ try{ var app=el.closest('[data-kee-app]'); if(app&&app._keeApi){ app._keeApi.setSelectedSectionId(el.value||''); return app._keeApi.startSelectedSection(); }}catch(e){ log('select fallback error', e); } return false; };})();