Files
jap_web/jwl/assets/index-CzXGdRG9.js
T
2026-07-03 13:06:14 +03:00

143 lines
28 KiB
JavaScript

(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=null,t=null,n=0,r=new Map;function i(t,i){return new Promise((a,o)=>{if(!e){o(Error(`Worker не создан`));return}let s=++n;r.set(s,{resolve:a,reject:o}),e.postMessage({type:t,id:s,...i})})}function a(){return t||(t=new Promise((t,i)=>{e=new Worker(new URL(`/assets/db-worker-DEWOyJFk.js`,``+import.meta.url),{type:`module`}),e.onmessage=e=>{let t=e.data;if(t.type!==`ready`&&t.type===`result`){let e=r.get(t.id);if(!e)return;r.delete(t.id),t.ok?e.resolve(t.data):(console.log(t),e.reject(Error(t.error||`Неизвестная ошибка Worker`)))}},e.onerror=e=>{console.error(`[SQLite Worker] Ошибка:`,e),i(Error(`Ошибка Worker`))};let a=++n;r.set(a,{resolve:t,reject:i}),e.postMessage({type:`init`,id:a})}),t)}async function o(){await a(),console.log(`[SQLite] База данных инициализирована.`)}async function s(e,t){return await i(`execute`,{sql:e,params:t})}async function c(e,t){await i(`executeVoid`,{sql:e,params:t})}async function l(){return await i(`getFile`)}async function u(){e&&(await i(`closeDb`),e.terminate(),e=null,t=null)}async function ee(e){await te(`replaceFile`,e)}async function te(t,i){return new Promise((a,o)=>{if(!e){o(Error(`Worker не создан`));return}let s=++n;r.set(s,{resolve:a,reject:o}),e.postMessage({type:t,id:s,params:i},[i])})}var d=d===void 0?{}:d,f={},p;for(p in d)d.hasOwnProperty(p)&&(f[p]=d[p]);var m=d.printErr||console.warn.bind(console);for(p in f)f.hasOwnProperty(p)&&(d[p]=f[p]);var h=(e,t)=>{throw t};f=null,d.arguments&&d.arguments,d.thisProgram&&(thisProgram=d.thisProgram),d.quit&&(h=d.quit),typeof WebAssembly!=`object`&&w(`no native wasm support detected`);var g,_=!1,v,ne;function y(){var e=g.buffer;d.HEAP8=new Int8Array(e),d.HEAPU8=ne=new Uint8Array(e)}var b=[],x=[],re=[];function ie(){if(d.preRun)for(typeof d.preRun==`function`&&(d.preRun=[d.preRun]);d.preRun.length;)se(d.preRun.shift());E(b)}function ae(){E(x)}function oe(){if(d.postRun)for(typeof d.postRun==`function`&&(d.postRun=[d.postRun]);d.postRun.length;)le(d.postRun.shift());E(re)}function se(e){b.unshift(e)}function ce(e){x.unshift(e)}function le(e){re.unshift(e)}var S=0,C=null;function ue(e){var t;S++,(t=d.monitorRunDependencies)==null||t.call(d,S)}function de(e){var t;if(S--,(t=d.monitorRunDependencies)==null||t.call(d,S),S==0&&C){var n=C;C=null,n()}}function w(e){var t;throw(t=d.onAbort)==null||t.call(d,e),e=`Aborted(`+e+`)`,m(e),_=!0,e+=`. Build with -sASSERTIONS for more info.`,new WebAssembly.RuntimeError(e)}function fe(){return{a:Te}}function pe(e){return fetch(e,{credentials:`same-origin`}).then(function(t){if(!t.ok)throw`failed to load wasm binary file at '`+e+`'`;return t.arrayBuffer()})}function me(e){var t=fe();function n(e,t){return N=e.exports,g=N.f,y(),ce(N.g),de(`wasm-instantiate`),N}ue(`wasm-instantiate`);function r(e){n(e.instance)}function i(n){return pe(e).then(function(e){return WebAssembly.instantiate(e,t)}).then(n,function(e){m(`failed to asynchronously prepare wasm: `+e),w(e)})}function a(){return e&&e.byteLength>0?WebAssembly.instantiate(e,t).then(r,function(e){m(`wasm compile failed: `+e)}):typeof WebAssembly.instantiateStreaming==`function`&&typeof e==`string`&&typeof fetch==`function`?fetch(e,{credentials:`same-origin`}).then(function(e){return WebAssembly.instantiateStreaming(e,t).then(r,function(e){return m(`wasm streaming compile failed: `+e),m(`falling back to ArrayBuffer instantiation`),i(r)})}):i(r)}if(d.instantiateWasm)try{return d.instantiateWasm(t,n)}catch(e){return m(`Module.instantiateWasm callback failed with error: `+e),!1}return a(),{}}var T=class{constructor(e){this.name=`ExitStatus`,this.message=`Program terminated with exit(${e})`,this.status=e}},E=e=>{for(;e.length>0;)e.shift()(d)},D=d.noExitRuntime||!0,he=()=>w(``),O=0,ge=()=>{D=!1,O=0},k={},A=e=>{if(e instanceof T||e==`unwind`)return v;h(1,e)},j=()=>D||O>0,M=e=>{var t;v=e,j()||((t=d.onExit)==null||t.call(d,e),_=!0),h(e,new T(e))},_e=(e,t)=>{v=e,M(e)},ve=()=>{if(!j())try{_e(v)}catch(e){A(e)}},ye=e=>{if(!_)try{e(),ve()}catch(e){A(e)}},be=()=>performance.now(),xe=(e,t)=>(k[e]&&(clearTimeout(k[e].id),delete k[e]),t&&(k[e]={id:setTimeout(()=>{delete k[e],ye(()=>ze(e,be()))},t),timeout_ms:t}),0),Se=()=>2147483648,Ce=(e,t)=>Math.ceil(e/t)*t,we=e=>{var t=(e-g.buffer.byteLength+65535)/65536|0;try{return g.grow(t),y(),1}catch{}},Te={c:he,b:ge,d:xe,e:e=>{var t=ne.length;e>>>=0;var n=Se();if(e>n)return!1;for(var r=1;r<=4;r*=2){var i=t*(1+.2/r);if(i=Math.min(i,e+100663296),we(Math.min(n,Ce(Math.max(e,i),65536))))return!0}return!1},a:M},N,Ee=d._ZSTD_isError=e=>(Ee=d._ZSTD_isError=N.h)(e),De=d._ZSTD_compressBound=e=>(De=d._ZSTD_compressBound=N.i)(e),Oe=d._ZSTD_createCCtx=()=>(Oe=d._ZSTD_createCCtx=N.j)(),ke=d._ZSTD_freeCCtx=e=>(ke=d._ZSTD_freeCCtx=N.k)(e),Ae=d._ZSTD_compress_usingDict=(e,t,n,r,i,a,o,s)=>(Ae=d._ZSTD_compress_usingDict=N.l)(e,t,n,r,i,a,o,s),je=d._ZSTD_compress=(e,t,n,r,i)=>(je=d._ZSTD_compress=N.m)(e,t,n,r,i),Me=d._ZSTD_createDCtx=()=>(Me=d._ZSTD_createDCtx=N.n)(),Ne=d._ZSTD_freeDCtx=e=>(Ne=d._ZSTD_freeDCtx=N.o)(e),Pe=d._ZSTD_getFrameContentSize=(e,t)=>(Pe=d._ZSTD_getFrameContentSize=N.p)(e,t),Fe=d._ZSTD_decompress_usingDict=(e,t,n,r,i,a,o)=>(Fe=d._ZSTD_decompress_usingDict=N.q)(e,t,n,r,i,a,o),Ie=d._ZSTD_decompress=(e,t,n,r)=>(Ie=d._ZSTD_decompress=N.r)(e,t,n,r),Le=d._malloc=e=>(Le=d._malloc=N.s)(e),Re=d._free=e=>(Re=d._free=N.t)(e),ze=(e,t)=>(ze=N.v)(e,t),P;C=function e(){P||Be(),P||(C=e)};function Be(){if(S>0||(ie(),S>0))return;function e(){var e;P||(P=!0,d.calledRun=!0,!_&&(ae(),(e=d.onRuntimeInitialized)==null||e.call(d),oe()))}d.setStatus?(d.setStatus(`Running...`),setTimeout(()=>{setTimeout(()=>d.setStatus(``),1),e()},1)):e()}if(d.run=Be,d.preInit)for(typeof d.preInit==`function`&&(d.preInit=[d.preInit]);d.preInit.length>0;)d.preInit.pop()();d.init=me;var Ve=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})},He=new Promise(e=>{d.onRuntimeInitialized=e}),Ue=()=>Ve(void 0,void 0,void 0,function*(){yield He}),F=e=>{let t=d._ZSTD_isError;return t(e)},We=(e,t)=>{let n=d._ZSTD_getFrameContentSize;return n(e,t)},Ge=(e,t={defaultHeapSize:1024*1024})=>{let n=d._malloc,r=n(e.byteLength);d.HEAP8.set(e,r);let i=We(r,e.byteLength),a=i===-1?t.defaultHeapSize:i,o=d._free,s=n(a);try{let t=d._ZSTD_decompress,n=t(s,a,r,e.byteLength);if(F(n))throw Error(`Failed to compress with code ${n}`);let i=new Uint8Array(d.HEAPU8.buffer,s,n).slice();return o(s,a),o(r,e.byteLength),i}catch(t){throw o(s,a),o(r,e.byteLength),t}},Ke=e=>{let t=d._ZSTD_compressBound;return t(e)},qe=(e,t)=>{let n=Ke(e.byteLength),r=d._malloc,i=r(n),a=r(e.byteLength);d.HEAP8.set(e,a);let o=d._free;try{let r=d._ZSTD_compress,s=r(i,n,a,e.byteLength,t??3);if(F(s))throw Error(`Failed to compress with code ${s}`);let c=new Uint8Array(d.HEAPU8.buffer,i,s).slice();return o(i,n),o(a,e.byteLength),c}catch(t){throw o(i,n),o(a,e.byteLength),t}},Je=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})},I=e=>Je(void 0,void 0,void 0,function*(){let t=new URL(`/assets/zstd-CV7N7xSg.wasm`,``+import.meta.url).href;d.init(e??t),yield Ue()});async function Ye(e,t,n,r){try{let i=await r(),a=new Uint8Array(i);await n(),await I();let o=qe(a),s=new FormData,c=new Blob([o.buffer],{type:`application/octet-stream`});s.append(`db`,c,`database.sqlite.zst`);let l=`${t.replace(/\/+$/,``)}/upload/${encodeURIComponent(e)}`,u=await fetch(l,{method:`POST`,body:s});if(!u.ok)throw Error(`Ошибка загрузки: сервер ответил ${u.status} ${u.statusText}`);console.log(`[Sync] База данных успешно загружена.`)}catch(e){throw console.error(`[Sync] Ошибка загрузки:`,e),console.log(e),alert(`Ошибка загрузки: ${e instanceof Error?e.message:String(e)}`),e}}async function Xe(e,t,n,r){try{let i=`${t.replace(/\/+$/,``)}/download/${encodeURIComponent(e)}`;console.log(i);let a=await fetch(i);if(!a.ok)throw Error(`Ошибка скачивания: сервер ответил ${a.status} ${a.statusText}`);let o=await a.arrayBuffer(),s=new Uint8Array(o);await I(),await n(Ge(s).buffer),await r(),console.log(`[Sync] База данных успешно скачана и применена.`)}catch(e){throw console.error(`[Sync] Ошибка скачивания:`,e),alert(`Ошибка скачивания: ${e instanceof Error?e.message:String(e)}`),e}}function Ze(e){if(typeof e==`number`)return e;if(typeof e==`string`){let t=Date.parse(e);if(!isNaN(t))return Math.floor(t/1e3)}return Math.floor(Date.now()/1e3)}async function L(){return(await s(`
SELECT id, name, forward, backward, filter
FROM card_set
ORDER BY id ASC
`)).map(e=>({id:e[0],name:e[1],forward:e[2],backward:e[3],filter:e[4]}))}async function Qe(){return(await s(`
SELECT id, key, value, tags, more, group_id
FROM words
ORDER BY id ASC
`)).map(e=>({id:e[0],key:e[1],value:e[2],tags:e[3],more:e[4],group_id:e[5]}))}async function $e(e){let t=await s(`SELECT w.id, w.key, w.value, w.tags, w.more, w.group_id
FROM words w
INNER JOIN card_stats cs ON cs.word_id = w.id
WHERE cs.set_id = ?
ORDER BY w.id ASC`,[e]),n=await s(`SELECT id, word_id, set_id, score, last_opened
FROM card_stats
WHERE set_id = ?
ORDER BY word_id ASC`,[e]);return{words:t.map(e=>({id:e[0],key:e[1],value:e[2],tags:e[3],more:e[4],group_id:e[5]})),stats:n.map(e=>({id:e[0],word_id:e[1],set_id:e[2],score:e[3],last_opened:Ze(e[4])}))}}async function R(e,t,n){await c(`UPDATE card_stats SET score = ?, last_opened = ? WHERE id = ?`,[Math.max(1,Math.round(t||1)),n,e])}async function et(){return(await s(`
SELECT id, name FROM word_group ORDER BY id ASC
`)).map(e=>({id:e[0],name:e[1]}))}async function tt(){let e=await et(),t=await Qe(),n=[];for(let r of e){let e=t.filter(e=>e.group_id===r.id),i=new Map;for(let t of e){if(!t.tags||t.tags.trim()===``){let e=`(без тега)`;i.has(e)||i.set(e,[]),i.get(e).push(t);continue}let e=t.tags.split(`,`).map(e=>e.trim()).filter(Boolean);for(let n of e)i.has(n)||i.set(n,[]),i.get(n).push(t)}let a=[];for(let[e,t]of i)a.push({tag:e,words:t});a.sort((e,t)=>t.words.length-e.words.length),n.push({group:r,tags:a})}return n}var nt=20,rt=.33,z=25,it=.95,B={Default:`Default`,TrainWorstFirst:`TrainWorstFirst`,FullRandom:`FullRandom`};function V(e){let t=(Date.now()-e.last_open)/(1e3*60*60*24),n=it**+Math.max(0,t);return e.score*n}function at(e,t){let n=V(e);switch(t){case`easy`:e.score=Math.round(n+5);break;case`ok`:e.score=Math.round(n+2);break;case`hard`:e.score=Math.round(n-1);break;case`none`:e.score=Math.round(n*.5);break}e.score<1?e.score=1:e.score>z&&(e.score=z),e.last_open=Date.now()}var ot=class{weights;constructor(e){this.weights=[...e]}sample(){let e=this.weights.reduce((e,t)=>e+t,0),t=Math.random()*e;for(let e=0;e<this.weights.length;e++)if(t-=this.weights[e],t<=0)return e;return this.weights.length-1}updateWeight(e,t){this.weights[e]=t}},st=class{backet=[];initialized=!1;next(e){return this.backet.length===0&&(this.backet=Array.from({length:e.words.length},(e,t)=>t),this.shuffle(this.backet)),this.backet.pop()}open(e,t,n){}init(e){this.initialized=!0,this.backet=Array.from({length:e.words.length},(e,t)=>t),this.shuffle(this.backet)}shuffle(e){for(let t=e.length-1;t>0;t--){let n=Math.floor(Math.random()*(t+1));[e[t],e[n]]=[e[n],e[t]]}}},ct=class{history=[];lastWeights;initialized=!1;next(e){let t=this.historyLen(e.set.length);for(let n=0;n<100;n++){let n=this.lastWeights.sample();if(!this.history.includes(n)||t>=e.set.length)return this.history.length>=t&&this.history.shift(),this.history.push(n),n}this.history=[];let n=this.lastWeights.sample();return this.history.push(n),n}open(e,t,n){let r=(100/Math.max(.1,V(n)))**2;this.lastWeights.updateWeight(t,r)}init(e){this.initialized=!0;let t=e.set.map(e=>(100/Math.max(.1,V(e)))**2*2);this.lastWeights=new ot(t)}historyLen(e){return Math.min(nt,Math.max(0,Math.floor(e*rt)))}},lt=class{initialized=!1;next(e){throw Error(`Not implemented`)}open(e,t,n){throw Error(`Not implemented`)}init(e){throw Error(`Not implemented`)}},ut=class{words;set;currentWordIndex=null;orderModule;constructor(e,t,n){this.words=t,this.set=n;let r=e.open_mode;this.orderModule=r===B.FullRandom?{type:`RandomSRS`,module:new st}:r===B.TrainWorstFirst?{type:`WorstWordsSRS`,module:new lt}:{type:`SemiRandomSRS`,module:new ct}}getOrderContext(){return{words:this.words,set:this.set}}next(){let e=this.getOrderContext(),t,n=this.orderModule;switch(n.type){case`SemiRandomSRS`:n.module.initialized||n.module.init(e),t=n.module.next(e);break;case`RandomSRS`:n.module.initialized||n.module.init(e),t=n.module.next(e);break;case`WorstWordsSRS`:n.module.initialized||n.module.init(e),t=n.module.next(e);break}return this.currentWordIndex=t,{word:this.words[t],stat:this.set[t]}}open(e){if(this.currentWordIndex===null)return;let t=this.currentWordIndex,n=this.set[t];at(n,e);let r=this.orderModule;switch(r.type){case`SemiRandomSRS`:r.module.open(e,t,n);break;case`RandomSRS`:r.module.open(e,t,n);break;case`WorstWordsSRS`:r.module.open(e,t,n);break}}get length(){return this.set.length}};function H(e,t){let n={key:e.key,value:e.value,tags:e.tags};return t.split(/\s+/).filter(Boolean).map(t=>t in n&&n[t]?n[t]:t in e.more&&e.more[t]?e.more[t]:``).filter(Boolean)}var dt=class{container;cardSet;words;callbacks;totalCards;forwardFields;backwardFields;seenWords=new Set;shownCount=0;constructor(e,t,n,r,i){this.container=e,this.callbacks=i,this.totalCards=r.length,this.forwardFields=t.forward||`key`,this.backwardFields=t.backward||`value`;let a=n.map(e=>({id:e.id,key:e.key,value:e.value,tags:e.tags,additional:e.more,group_id:e.group_id}));this.words=n,this.cardSet=new ut(t,a,r),this.render()}render(){let e=this.totalCards;this.container.innerHTML=`
<div class="training-header">
<button id="btnTrainingBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
<span style="font-size:0.85rem;color:var(--text);">уникальных: 0 / ${e}</span>
</div>
<div id="training-card" class="training-card">
<div class="training-content" id="training-content">
</div>
</div>
<div id="training-actions" class="training-actions">
</div>
`,this.renderCard(),document.getElementById(`btnTrainingBack`)?.addEventListener(`click`,()=>{this.callbacks.onFinish()})}updateCounter(){let e=this.totalCards,t=this.container.querySelector(`.training-header span`);t&&(t.textContent=`уникальных: ${this.seenWords.size} / ${e}`)}renderCard(){let e=this.cardSet.next(),t=this.words.find(t=>t.id===e.word.id);if(!t)return;this.shownCount++,this.seenWords.has(e.word.id)||this.seenWords.add(e.word.id),this.updateCounter();let n=this.cardSet.set[this.cardSet.currentWordIndex];if(n){let e=Math.floor(Date.now()/1e3);n.last_open=Date.now(),R(n.id,n.score,e).catch(e=>console.error(`[Training] Ошибка сохранения last_opened:`,e))}this.renderSide(t,this.forwardFields,this.backwardFields,!1,n),this.renderActions(!1)}renderSide(e,t,n,r,i){let a=document.getElementById(`training-content`);if(!a)return;let o=``;if(i&&(o=`⚡ ${V(i).toFixed(1)}`),r){let r=H(e,t),i=H(e,n);a.innerHTML=`
<div class="training-side">
${r.length>0?r.map(e=>`<div class="training-field">${this.escapeHtml(e)}</div>`).join(``):`<div class="training-field" style="opacity:0.4;">—</div>`}
</div>
<div class="training-divider"></div>
<div class="training-side training-side-back">
${i.length>0?i.map(e=>`<div class="training-field">${this.escapeHtml(e)}</div>`).join(``):`<div class="training-field" style="opacity:0.4;">—</div>`}
</div>
${o?`<div class="training-score">${o}</div>`:``}
`}else{let n=H(e,t);if(n.length===0){a.innerHTML=`
<div class="training-field" style="opacity:0.4;">Нет данных для отображения</div>
${o?`<div class="training-score">${o}</div>`:``}
`;return}a.innerHTML=`
${n.map(e=>`<div class="training-field">${this.escapeHtml(e)}</div>`).join(``)}
${o?`<div class="training-score">${o}</div>`:``}
`}}renderActions(e){let t=document.getElementById(`training-actions`);t&&(e?(t.innerHTML=`
<div class="training-vote">
<button class="btn vote-btn vote-easy" data-mode="easy">🟢 Легко</button>
<button class="btn vote-btn vote-ok" data-mode="ok">🟡 Нормально</button>
<button class="btn vote-btn vote-hard" data-mode="hard">🟠 Тяжело</button>
<button class="btn vote-btn vote-none" data-mode="none">🔴 Не знаю</button>
</div>
`,t.querySelectorAll(`.vote-btn`).forEach(e=>{e.addEventListener(`click`,()=>{let t=e.dataset.mode;this.vote(t)})})):(t.innerHTML=`
<button id="btnReveal" class="btn btn-primary" style="flex:none; width:100%;">Показать перевод</button>
`,document.getElementById(`btnReveal`)?.addEventListener(`click`,()=>this.reveal())))}reveal(){let e=this.cardSet.currentWordIndex;if(e===null)return;let t=this.words[e];if(!t)return;let n=this.cardSet.set[e];this.renderSide(t,this.forwardFields,this.backwardFields,!0,n),this.renderActions(!0)}async vote(e){this.cardSet.open(e);let t=this.cardSet.currentWordIndex;if(t===null)return;let n=this.cardSet.set[t];if(n)try{await R(n.id,n.score,Math.floor(n.last_open/1e3))}catch(e){console.error(`[Training] Ошибка сохранения:`,e)}this.renderCard()}escapeHtml(e){let t=document.createElement(`div`);return t.textContent=e,t.innerHTML}};function ft(e){if(!e)return{};try{return JSON.parse(e)}catch{return{}}}function pt(e,t,n){let r=ft(t.more);e.innerHTML=`
<div class="word-view-header">
<button id="btnViewBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
</div>
<div class="word-view-card">
<div class="word-view-section">
<div class="word-view-label">Японское слово</div>
<div class="word-view-field">${U(t.key)}</div>
</div>
<div class="word-view-divider"></div>
<div class="word-view-section">
<div class="word-view-label">Перевод</div>
<div class="word-view-field">${U(t.value)}</div>
</div>
<div class="word-view-divider"></div>
<div class="word-view-section">
<div class="word-view-label">Теги</div>
<div class="word-view-field" style="font-size:0.9rem;color:var(--accent);">
${t.tags?U(t.tags):`—`}
</div>
</div>
${Object.keys(r).length>0?`
<div class="word-view-divider"></div>
<div class="word-view-section">
<div class="word-view-label">Дополнительные данные</div>
${Object.entries(r).map(([e,t])=>`
<div class="word-view-more-row">
<span class="word-view-more-key">${U(e)}</span>
<span class="word-view-more-value">${U(t)}</span>
</div>
`).join(``)}
</div>
`:``}
</div>
`,document.getElementById(`btnViewBack`)?.addEventListener(`click`,()=>{n.onBack()})}function U(e){let t=document.createElement(`div`);return t.textContent=e,t.innerHTML}var W=`https://learning.micialware.ru`,G=`japlearn_sync_id`,K=document.getElementById(`page-container`),q=document.querySelectorAll(`.nav-btn`),J=`words`;function Y(e){let t=document.createElement(`div`);return t.textContent=e,t.innerHTML}function X(e,t=!1){let n=document.getElementById(`status`);n&&(n.textContent=e,n.className=`status-bar`,t?n.classList.add(`error`):n.classList.add(`success`)),console.log(`[App] ${e}`)}function mt(){let e=document.getElementById(`syncId`)?.value.trim();if(!e)throw Error(`Введите ID синхронизации`);return localStorage.setItem(G,e),e}function ht(){let e=localStorage.getItem(G);if(e){let t=document.getElementById(`syncId`);t&&(t.value=e)}}function gt(){K.innerHTML=`
<div class="card">
<h2 class="card-title">📡 Синхронизация</h2>
<div class="sync-input-group">
<input type="text" id="syncId" placeholder="Введите ID синхронизации" autocomplete="off" spellcheck="false" />
<div class="sync-buttons">
<button id="btnDownload" class="btn">📥 Скачать</button>
<button id="btnUpload" class="btn btn-primary">📤 Загрузить</button>
</div>
</div>
</div>
<div class="card" style="flex: 1;">
<h2 class="card-title">📚 Слова</h2>
<div id="word-groups"></div>
</div>
<div id="status" class="status-bar">Загрузка...</div>
`}function _t(e){let t=document.getElementById(`word-groups`);if(!t)return;if(t.innerHTML=``,e.length===0){t.innerHTML=`<p style="text-align:center;opacity:0.6;padding:1rem;">Нет слов 🎉</p>`;return}let n=document.createElement(`div`);n.className=`group-tabs`;let r=document.createElement(`div`);r.className=`group-panels`;for(let t=0;t<e.length;t++){let i=e[t],a=document.createElement(`button`);a.className=`group-tab`+(t===0?` active`:``);let o=i.tags.reduce((e,t)=>e+t.words.length,0);a.innerHTML=`${Y(i.group.name)} <span class="group-tab-count">${o}</span>`;let s=document.createElement(`div`);s.className=`group-panel`+(t===0?` active`:``);for(let e of i.tags){let t=document.createElement(`div`);t.className=`word-tag-block`;let n=document.createElement(`div`);n.className=`word-tag-header`,n.innerHTML=`
<span class="word-tag-label">#${Y(e.tag)}</span>
<span class="word-tag-count">${e.words.length}</span>
`;let r=!0,i=document.createElement(`div`);i.className=`word-tag-words`,i.style.display=`none`,n.addEventListener(`click`,()=>{r=!r,i.style.display=r?`none`:``,n.classList.toggle(`collapsed`,r)});for(let t of e.words){let e=document.createElement(`div`);e.className=`word-item`,e.innerHTML=`
<span class="word-key">${Y(t.key)}</span>
<span class="word-value">${Y(t.value)}</span>
`,e.addEventListener(`click`,e=>{e.stopPropagation(),xt(t)}),i.appendChild(e)}t.appendChild(n),t.appendChild(i),s.appendChild(t)}a.addEventListener(`click`,()=>{n.querySelectorAll(`.group-tab`).forEach(e=>e.classList.remove(`active`)),r.querySelectorAll(`.group-panel`).forEach(e=>e.classList.remove(`active`)),a.classList.add(`active`),s.classList.add(`active`)}),n.appendChild(a),r.appendChild(s)}t.appendChild(n),t.appendChild(r)}function vt(){K.innerHTML=`
<div class="card" style="flex:1; display:flex; flex-direction:column; padding:0; overflow:hidden;">
<div class="sets-layout" style="flex:1;">
<ul id="sets-list" class="sets-list"></ul>
<div id="sets-detail" class="sets-detail">
<div class="placeholder-area">
<div class="placeholder-icon">🗂️</div>
<div>Выберите колоду</div>
</div>
</div>
</div>
</div>
<div id="status" class="status-bar">Загрузка...</div>
`}function yt(e){let t=document.getElementById(`sets-list`);if(!t)return;if(t.innerHTML=``,e.length===0){t.innerHTML=`<li style="opacity:0.5;text-align:center;padding:20px;">Нет колод</li>`;return}for(let n of e){let e=document.createElement(`li`);e.textContent=n.name,e.dataset.setId=String(n.id),e.addEventListener(`click`,()=>bt(n,e)),t.appendChild(e)}let n=t.querySelector(`li`);if(n){n.classList.add(`selected`);let t=Number(n.dataset.setId),r=e.find(e=>e.id===t);r&&bt(r,n)}}function bt(e,t){document.querySelectorAll(`#sets-list li`).forEach(e=>e.classList.remove(`selected`)),t.classList.add(`selected`);let n=document.getElementById(`sets-detail`);n&&(n.innerHTML=`
<h3>${Y(e.name)}</h3>
<p><strong>Прямой порядок:</strong> ${Y(e.forward)}</p>
<p><strong>Обратный порядок:</strong> ${Y(e.backward)}</p>
<p><strong>Фильтр:</strong> ${Y(e.filter)}</p>
<label style="font-size:0.8rem;color:var(--text);display:block;margin-top:12px;">Режим тренировки</label>
<select id="mode-select" class="mode-select">
<option value="${B.Default}">${B.Default} — взвешенная случайность</option>
<option value="${B.TrainWorstFirst}">${B.TrainWorstFirst} — слабые слова</option>
<option value="${B.FullRandom}">${B.FullRandom} — полная случайность</option>
</select>
<button class="btn btn-primary btn-start-training" data-set-id="${e.id}">▶ Начать тренировку</button>
`)}function xt(e){Ct(),J=`wordview`,pt(K,e,{onBack:()=>{Q(),Z(`words`)}})}async function St(e){let t=document.getElementById(`mode-select`)?.value||B.Default,n=(await L()).find(t=>t.id===e);if(!n){X(`Колода не найдена`,!0);return}let{words:r,stats:i}=await $e(e);if(r.length===0){X(`Нет слов для тренировки`,!0);return}console.log(`[Training] Запуск тренировки: ${r.length} слов, ${i.length} записей, режим: ${t}`);let a={id:n.id,name:n.name,forward:n.forward,backward:n.backward,filter:n.filter,count:null,worst_words_list:null,open_mode:t},o=r.map(e=>{let t={};if(e.more)try{t=JSON.parse(e.more)}catch{}return{id:e.id,key:e.key,value:e.value,tags:e.tags,more:t,group_id:e.group_id}}),s=i.map(e=>({id:e.id,word_id:e.word_id,set_id:e.set_id,last_open:e.last_opened*1e3,score:e.score}));Ct(),J=`training`,K.innerHTML=`<div id="training-root" style="flex:1;display:flex;flex-direction:column;"></div>`,new dt(document.getElementById(`training-root`),a,o,s,{onFinish:()=>{Q(),Z(`sets`)}})}function Z(e){J=e,Q(),q.forEach(t=>{t.classList.toggle(`active`,t.dataset.page===e)}),e===`words`?(gt(),$()):(vt(),wt())}function Ct(){document.getElementById(`app-nav`).style.display=`none`}function Q(){document.getElementById(`app-nav`).style.display=`flex`}async function $(){try{let e=await tt();_t(e),X(`Слов: ${e.reduce((e,t)=>e+t.tags.reduce((e,t)=>e+t.words.length,0),0)}`)}catch(e){X(`Ошибка загрузки слов`,!0),console.error(e)}}async function wt(){try{let e=await L();yt(e),X(`Колод: ${e.length}`)}catch(e){X(`Ошибка загрузки колод`,!0),console.error(e)}}async function Tt(){try{let e=mt(),t=document.getElementById(`btnDownload`);t&&(t.disabled=!0),X(`Скачивание...`),await Xe(e,W,ee,o),J===`words`&&await $(),X(`База данных скачана.`)}catch{}finally{let e=document.getElementById(`btnDownload`);e&&(e.disabled=!1)}}async function Et(){try{let e=mt(),t=document.getElementById(`btnUpload`);t&&(t.disabled=!0),X(`Загрузка...`),await Ye(e,W,u,l),await o(),J===`words`&&await $(),X(`База данных загружена.`)}catch{}finally{let e=document.getElementById(`btnUpload`);e&&(e.disabled=!1)}}async function Dt(){try{await o(),console.log(`[App] База данных инициализирована.`),q.forEach(e=>{e.addEventListener(`click`,()=>{J!==`training`&&Z(e.dataset.page)})}),Z(`words`),ht(),K.addEventListener(`click`,async e=>{let t=e.target;t.id===`btnDownload`&&await Tt(),t.id===`btnUpload`&&await Et();let n=t.closest(`.btn-start-training`);n&&await St(Number(n.dataset.setId))})}catch(e){console.error(`[App] Ошибка инициализации:`,e)}}Dt();