(function(){
var words=[];
var __vnCfg=window.dnVnCfg||{};
var categories=__vnCfg.categories||[];
var vnSoundUrl=__vnCfg.soundUrl||'';
var vnTtsConfig=__vnCfg.tts||{};
var vnWordsEndpoint=__vnCfg.endpoint||'';
var vnWordsLoaded=false, vnWordsLoading=false, vnWordsCallbacks=[];
function vnEnsureWords(cb){
if(vnWordsLoaded){ if(cb) cb(); return; }
if(cb) vnWordsCallbacks.push(cb);
if(vnWordsLoading||!window.fetch||!vnWordsEndpoint) return;
vnWordsLoading=true;
fetch(vnWordsEndpoint, { credentials: 'same-origin' })
.then(function(r){ return r&&r.ok ? r.json():null; })
.then(function(data){
if(data&&data.length){ words=data; }
vnWordsLoaded=true; vnWordsLoading=false;
var cbs=vnWordsCallbacks.slice(); vnWordsCallbacks=[];
cbs.forEach(function(f){ try { f(); } catch (e){}});
})
.catch(function(){ vnWordsLoading=false; });
}
(function vnPrefetchTriggers(){
var sel=document.getElementById('vnCategorySelect');
if(sel){
['focus','mousedown','touchstart'].forEach(function(ev){
sel.addEventListener(ev, function(){ vnEnsureWords(); }, { once: true, passive: true });
});
}
var outer=document.getElementById('vn-widget-outer');
if(outer&&'IntersectionObserver' in window){
var io=new IntersectionObserver(function(entries){
if(entries.some(function(e){ return e.isIntersecting; })){ io.disconnect(); vnEnsureWords(); }}, { rootMargin: '200px 0px' });
io.observe(outer);
}})();
var currentCategoryId='';
var currentIndex=0;
var isPlaying=false;
var timer=null;
var elapsed=0;
var intervalMs=200;
var vnAwaitingCategorySelection=false;
var vnPrevWordCardState=null;
function vnClearWordCard(){
var ids=[ 'vnVietWord', 'vnPronunciation', 'vnKoreanMeaning', 'vnExampleViet', 'vnExampleKorean' ];
ids.forEach(function(id){
var el=document.getElementById(id);
if(el) el.textContent='';
});
}
var vnDisplayMode=(function(){
try {
var s=localStorage.getItem('dn_vn_display_mode');
return (s==='koreanViet'||s==='vietKorean') ? s:'vietKorean';
} catch (e){ return 'vietKorean'; }})();
function applyMode(){
var w=document.querySelector('.vn-wrap');
if(!w) return;
if(vnDisplayMode==='koreanViet') w.classList.add('vn-mode-hanbe');
else w.classList.remove('vn-mode-hanbe');
var btn=document.getElementById('vnModeToggleBtn');
if(btn){
btn.textContent=vnDisplayMode==='koreanViet' ? '한베':'베한';
btn.setAttribute('aria-pressed', vnDisplayMode==='koreanViet' ? 'true':'false');
}}
function getWords(){
if(!currentCategoryId) return [];
var catId=parseInt(currentCategoryId, 10);
return words.filter(function(w){
return w.category_ids&&w.category_ids.indexOf(catId)!==-1;
});
}
function getFiltered(){
return getWords();
}
function getInterval(){
return parseInt(document.getElementById('vnTimeSelect').value, 10) * 1000;
}
var vnCurrentAudio=null;
var vnTtsVoiceKey='dn_vn_tts_voice';
var vnTtsMutedKey='dn_vn_tts_muted';
function getStoredTtsVoice(){
try {
var v=localStorage.getItem(vnTtsVoiceKey);
if(v&&/^vi-VN-Wavenet-[A-D]$/.test(v)) return v;
} catch (e){}
return 'vi-VN-Wavenet-A';
}
function isTtsMuted(){
try { return localStorage.getItem(vnTtsMutedKey)==='1'; } catch (e){ return false; }}
function stopTtsAudio(){
if(vnCurrentAudio){
try {
vnCurrentAudio.pause();
vnCurrentAudio.src='';
vnCurrentAudio=null;
} catch (e){}}
if(typeof window.speechSynthesis!=='undefined'){
try { window.speechSynthesis.cancel(); } catch (e){}}
}
function speakWordFallback(text){
if(!text||typeof window.SpeechSynthesisUtterance==='undefined') return;
var u=new window.SpeechSynthesisUtterance(String(text).trim());
u.lang='vi-VN';
var voices=window.speechSynthesis.getVoices();
for (var i=0; i < voices.length; i++){
if(voices[i].lang==='vi-VN'||voices[i].lang==='vi'){
u.voice=voices[i];
break;
}}
u.rate=0.9;
window.speechSynthesis.speak(u);
}
function speakWordTts(text){
if(!text||String(text).trim()==='') return;
if(isTtsMuted()) return;
stopTtsAudio();
var voice=getStoredTtsVoice();
var t=String(text).trim();
if(!vnTtsConfig||!vnTtsConfig.configured){
speakWordFallback(t);
return;
}
var fd=new FormData();
fd.append('action', 'dn_vn_tts');
fd.append('nonce', vnTtsConfig.nonce);
fd.append('text', t);
fd.append('voice', voice);
fetch(vnTtsConfig.ajaxUrl, { method: 'POST', body: fd, credentials: 'same-origin' })
.then(function(res){ return res.json(); })
.then(function(data){
if(data.success&&data.data&&data.data.audio){
var a=new Audio('data:audio/mp3;base64,' + data.data.audio);
vnCurrentAudio=a;
a.playbackRate=0.9;
a.play().catch(function(){});
}})
.catch(function(){});
}
function applyTtsVoiceButtons(){
var v=getStoredTtsVoice();
document.querySelectorAll('.vn-tts-voice-btn').forEach(function(btn){
btn.classList.toggle('vn-tts-voice-active', btn.getAttribute('data-voice')===v);
});
}
function applyTtsMuteBtn(){
var btn=document.getElementById('vnTtsMuteBtn');
if(!btn) return;
var muted=isTtsMuted();
btn.classList.toggle('vn-tts-muted', muted);
btn.textContent=muted ? '🔇':'🔊';
btn.title=muted ? '발음 자동재생 켜기':'발음 자동재생 끄기';
}
function formatPronunciation(pron){
if(!pron) return '';
pron=String(pron).trim();
if(!pron) return '';
if(/^\[.+\]$/.test(pron)) return pron;
return '[' + pron + ']';
}
function showWord(idx){
var list=getFiltered();
if(!list||!list.length||!list[idx]) return;
var w=list[idx];
var card=document.querySelector('.vn-word-card');
if(card){
card.classList.remove('vn-fade-in');
void card.offsetWidth;
card.classList.add('vn-fade-in');
}
document.getElementById('vnVietWord').textContent=w.word||'';
document.getElementById('vnPronunciation').textContent=formatPronunciation(w.pronunciation);
document.getElementById('vnKoreanMeaning').textContent=w.meaning||'';
var exBox=document.querySelector('.vn-example-box');
if(exBox){
exBox.classList.remove('vn-fade-in');
void exBox.offsetWidth;
exBox.classList.add('vn-fade-in');
}
document.getElementById('vnExampleViet').textContent=w.example||'';
document.getElementById('vnExampleKorean').textContent=w.example_meaning||'';
currentIndex=idx;
if(!vnAwaitingCategorySelection){
speakWordTts(w.word||'');
}}
window.vnLoadCategory=function(){
vnAwaitingCategorySelection=false;
vnPrevWordCardState=null;
currentCategoryId=document.getElementById('vnCategorySelect').value||'';
currentIndex=0;
stopTimer();
if(!currentCategoryId){
stopTtsAudio();
vnClearWordCard();
resetProgress();
return;
}
if(!vnWordsLoaded){
vnEnsureWords(function(){ if(currentCategoryId) vnLoadCategory(); });
return;
}
var listAfter=getWords();
if(!listAfter.length){
stopTtsAudio();
vnClearWordCard();
resetProgress();
return;
}
showWord(0);
resetProgress();
};
window.vnTogglePlay=function(){
if(isPlaying){ vnPause(); }else{ vnPlay(); }};
function vnPlay(){
var list=getFiltered();
if(!list||!list.length) return;
isPlaying=true;
document.getElementById('vnPlayBtn').classList.add('vn-hidden');
document.getElementById('vnPauseBtn').classList.remove('vn-hidden');
startTimer();
}
function vnPause(){
isPlaying=false;
document.getElementById('vnPauseBtn').classList.add('vn-hidden');
document.getElementById('vnPlayBtn').classList.remove('vn-hidden');
stopTimer();
}
function stopTimer(){ clearInterval(timer); timer=null; }
function resetProgress(){
elapsed=0;
document.getElementById('vnProgressBar').style.width='100%';
}
function startTimer(){
var totalMs=getInterval();
if(elapsed===0) document.getElementById('vnProgressBar').style.width='100%';
timer=setInterval(function(){
elapsed +=intervalMs;
var remaining=Math.max(0, totalMs - elapsed);
document.getElementById('vnProgressBar').style.width=((remaining / totalMs) * 100) + '%';
if(elapsed >=totalMs) nextWord();
}, intervalMs);
}
function playWordChangeSound(){
if(vnSoundUrl){
var a=new Audio(vnSoundUrl);
a.play().catch(function(){});
}}
function nextWord(){
stopTimer();
var list=getFiltered();
if(!list||!list.length){
vnPause();
return;
}
currentIndex=(currentIndex + 1) % list.length;
elapsed=0;
showWord(currentIndex);
playWordChangeSound();
if(isPlaying) startTimer();
}
window.vnResetCard=function(){
vnPause();
currentIndex=0;
elapsed=0;
if(vnAwaitingCategorySelection){
vnClearWordCard();
resetProgress();
return;
}
if(!currentCategoryId){
stopTtsAudio();
vnClearWordCard();
resetProgress();
return;
}
if(!getFiltered().length){
stopTtsAudio();
vnClearWordCard();
resetProgress();
return;
}
showWord(0);
resetProgress();
};
document.getElementById('vnCategorySelect').addEventListener('change', function(){
vnLoadCategory();
var sel=document.getElementById('vnCategorySelect');
if(sel) sel.classList.toggle('vn-category-placeholder', sel.value==='');
});
(function(){
var sel=document.getElementById('vnCategorySelect');
if(sel) sel.classList.toggle('vn-category-placeholder', sel.value==='');
})();
document.getElementById('vnPlayBtn').addEventListener('click', vnTogglePlay);
document.getElementById('vnPauseBtn').addEventListener('click', vnTogglePlay);
document.getElementById('vnResetBtn').addEventListener('click', function(){
if(overlay&&!overlay.hasAttribute('hidden')) closeVietnameseFullscreen();
vnResetCard();
});
document.getElementById('vnModeToggleBtn').addEventListener('click', function(){
vnDisplayMode=vnDisplayMode==='vietKorean' ? 'koreanViet':'vietKorean';
try { localStorage.setItem('dn_vn_display_mode', vnDisplayMode); } catch (e){}
applyMode();
});
document.querySelectorAll('.vn-tts-voice-btn').forEach(function(btn){
btn.addEventListener('click', function(){
var v=btn.getAttribute('data-voice');
if(!v) return;
try { localStorage.setItem(vnTtsVoiceKey, v); } catch (e){}
applyTtsVoiceButtons();
});
});
document.getElementById('vnTtsMuteBtn').addEventListener('click', function(){
var muted = !isTtsMuted();
try { localStorage.setItem(vnTtsMutedKey, muted ? '1':'0'); } catch (e){}
stopTtsAudio();
applyTtsMuteBtn();
});
document.getElementById('vnPauseBtn').classList.add('vn-hidden');
applyMode();
applyTtsVoiceButtons();
applyTtsMuteBtn();
(function vnInitCard(){
var sel=document.getElementById('vnCategorySelect');
currentCategoryId=(sel&&sel.value) ? sel.value:'';
if(!currentCategoryId){
stopTtsAudio();
vnClearWordCard();
resetProgress();
}else if(!getWords().length){
stopTtsAudio();
vnClearWordCard();
resetProgress();
}else{
showWord(0);
resetProgress();
}})();
var overlay=document.getElementById('vn-fullscreen-overlay');
var fullscreenInner=document.getElementById('vn-fullscreen-inner');
var wrapEl=document.querySelector('.vn-wrap');
var widgetOuter=document.getElementById('vn-widget-outer');
window.openVietnameseWordFullscreen=function(){
if(!wrapEl||!overlay||!fullscreenInner||!widgetOuter) return;
vnEnsureWords();
vnAwaitingCategorySelection=true;
stopTtsAudio();
vnPause();
resetProgress();
vnPrevWordCardState={
categoryId: currentCategoryId,
index: currentIndex,
viet: (document.getElementById('vnVietWord')||{}).textContent||'',
pron: (document.getElementById('vnPronunciation')||{}).textContent||'',
meaning: (document.getElementById('vnKoreanMeaning')||{}).textContent||'',
exViet: (document.getElementById('vnExampleViet')||{}).textContent||'',
exKor: (document.getElementById('vnExampleKorean')||{}).textContent||'',
};
vnClearWordCard();
fullscreenInner.appendChild(wrapEl);
overlay.removeAttribute('hidden');
overlay.style.display='flex';
document.body.style.overflow='hidden';
var _fml=document.getElementById('fml-float-menu');
if(_fml){ _fml.dataset.fmlPrevDisplay=_fml.style.display||''; _fml.style.display='none'; }};
function closeVietnameseFullscreen(){
if(!wrapEl||!widgetOuter||!overlay) return;
widgetOuter.appendChild(wrapEl);
overlay.setAttribute('hidden', '');
document.body.style.overflow='';
var _fml=document.getElementById('fml-float-menu');
if(_fml){ _fml.style.display=_fml.dataset.fmlPrevDisplay||''; delete _fml.dataset.fmlPrevDisplay; }
if(vnAwaitingCategorySelection&&vnPrevWordCardState){
vnAwaitingCategorySelection=false;
var s=vnPrevWordCardState;
var map={
vnVietWord: s.viet,
vnPronunciation: s.pron,
vnKoreanMeaning: s.meaning,
vnExampleViet: s.exViet,
vnExampleKorean: s.exKor,
};
Object.keys(map).forEach(function(id){
var el=document.getElementById(id);
if(el) el.textContent=map[id]||'';
});
vnPrevWordCardState=null;
}}
})();