qwerty-cli 0.0.1-alpha.11 → 0.0.1-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/assets/sounds/key-default.wav +0 -0
  2. package/dist/{ConfigEditor-U4J6FVDN.js → ConfigEditor-HAP5C2Y7.js} +2 -2
  3. package/dist/{DictBrowser-DZZWZXEA.js → DictBrowser-7CIISVDN.js} +2 -2
  4. package/dist/{HelpScreen-7U5XF3IU.js → HelpScreen-P4U5O4OP.js} +2 -2
  5. package/dist/PracticeScreen-LM6M2OYD.js +2 -0
  6. package/dist/PracticeScreen-LM6M2OYD.js.map +1 -0
  7. package/dist/{StatsViewer-PDXZ7PJR.js → StatsViewer-3CUMIAV4.js} +2 -2
  8. package/dist/{WordLookup-EAXT7PGW.js → WordLookup-YIU6LP4H.js} +2 -2
  9. package/dist/chunk-7RMRK5MO.js +2 -0
  10. package/dist/chunk-7RMRK5MO.js.map +1 -0
  11. package/dist/chunk-KBRGNL2D.js +3 -0
  12. package/dist/chunk-KBRGNL2D.js.map +1 -0
  13. package/dist/chunk-R6HQWKXU.js +2 -0
  14. package/dist/chunk-R6HQWKXU.js.map +1 -0
  15. package/dist/chunk-ZXMHFRCR.js +3 -0
  16. package/dist/chunk-ZXMHFRCR.js.map +1 -0
  17. package/dist/cli.js +1 -1
  18. package/dist/cli.js.map +1 -1
  19. package/dist/doctor.impl-5UHLQ4SZ.js +4 -0
  20. package/dist/doctor.impl-5UHLQ4SZ.js.map +1 -0
  21. package/dist/menu.impl-PUAAZGHA.js +2 -0
  22. package/dist/{menu.impl-EQABPGB2.js.map → menu.impl-PUAAZGHA.js.map} +1 -1
  23. package/dist/practice.impl-CJLWRT5Z.js +2 -0
  24. package/dist/{practice.impl-NCY3HVPA.js.map → practice.impl-CJLWRT5Z.js.map} +1 -1
  25. package/package.json +1 -1
  26. package/dist/PracticeScreen-VLXPLVNW.js +0 -2
  27. package/dist/PracticeScreen-VLXPLVNW.js.map +0 -1
  28. package/dist/chunk-LN2WQT5M.js +0 -3
  29. package/dist/chunk-LN2WQT5M.js.map +0 -1
  30. package/dist/chunk-SSDQJ6MT.js +0 -2
  31. package/dist/chunk-SSDQJ6MT.js.map +0 -1
  32. package/dist/chunk-WE6IV5XB.js +0 -2
  33. package/dist/chunk-WE6IV5XB.js.map +0 -1
  34. package/dist/menu.impl-EQABPGB2.js +0 -2
  35. package/dist/practice.impl-NCY3HVPA.js +0 -2
  36. /package/dist/{ConfigEditor-U4J6FVDN.js.map → ConfigEditor-HAP5C2Y7.js.map} +0 -0
  37. /package/dist/{DictBrowser-DZZWZXEA.js.map → DictBrowser-7CIISVDN.js.map} +0 -0
  38. /package/dist/{HelpScreen-7U5XF3IU.js.map → HelpScreen-P4U5O4OP.js.map} +0 -0
  39. /package/dist/{StatsViewer-PDXZ7PJR.js.map → StatsViewer-3CUMIAV4.js.map} +0 -0
  40. /package/dist/{WordLookup-EAXT7PGW.js.map → WordLookup-YIU6LP4H.js.map} +0 -0
Binary file
@@ -1,2 +1,2 @@
1
- import{b as M}from"./chunk-2GTGXODM.js";import{d as F}from"./chunk-ELWVQGDK.js";import{b as N,c as v,e as P}from"./chunk-IUFBN3RD.js";import{b as R,d as $,f as a}from"./chunk-SSDQJ6MT.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useState as S}from"react";import{Box as p,Text as d,useInput as I}from"ink";import{jsx as c,jsxs as x}from"react/jsx-runtime";var f=[{kind:"dictRef",path:"defaultDict",labelKey:"defaultDict"},{kind:"enum",path:"defaultMode",labelKey:"defaultMode",options:["order","dictation","review","random","loop"]},{kind:"enum",path:"accent",labelKey:"accent",options:["us","uk"]},{kind:"enum",path:"language",labelKey:"language",options:["auto","zh","en"]},{kind:"enum",path:"mirror",labelKey:"mirror",options:["jsdelivr","github"]},{kind:"enum",path:"stealth",labelKey:"stealth",options:["off","menu","default"]},{kind:"int",path:"chapterSize",labelKey:"chapterSize",min:1,max:200},{kind:"bool",path:"autoplayPronunciation",labelKey:"autoplayPronunciation"},{kind:"bool",path:"sounds.master",labelKey:"soundsMaster"},{kind:"bool",path:"sounds.keystroke",labelKey:"soundsKeystroke"},{kind:"bool",path:"sounds.feedback",labelKey:"soundsFeedback"},{kind:"string",path:"sounds.keySoundName",labelKey:"soundsKeySound"}];function C(s,e){return e.split(".").reduce((r,l)=>{if(r&&typeof r=="object")return r[l]},s)}function U(){let s=R(),{cfg:e,setCfg:r}=M(),l=$(),y=N(e.defaultDict),[m,K]=S(0),[h,b]=S(!1),[w,g]=S(""),[A,u]=S(null),o=f[m],T=C(e,o.path),k=async i=>{try{let t=F(e,o.path,i);await r(t),b(!1),u(null)}catch(t){u(t.message)}};I((i,t)=>{if(h&&o.kind==="string"){if(t.escape){b(!1),u(null);return}if(t.return){k(w);return}if(t.backspace||t.delete){g(n=>n.slice(0,-1));return}i&&!t.ctrl&&!t.meta&&g(n=>n+i);return}if(h&&o.kind==="int"){if(t.escape){b(!1),u(null);return}if(t.return){k(w);return}if(t.backspace||t.delete){g(n=>n.slice(0,-1));return}/^[0-9]$/.test(i)&&g(n=>n+i);return}if(t.escape){s.back();return}if(t.upArrow){K(n=>(n-1+f.length)%f.length),u(null);return}if(t.downArrow){K(n=>(n+1)%f.length),u(null);return}if(o.kind==="bool"&&(i===" "||t.return)){k(T?"false":"true");return}if(o.kind==="enum"&&(t.leftArrow||t.rightArrow)){let n=o.options.indexOf(String(T)),D=t.rightArrow?1:-1,B=o.options[(n+D+o.options.length)%o.options.length];k(B);return}if(o.kind==="dictRef"&&t.return){s.navigate({name:"dict",params:{pickerMode:"set-default"}});return}(o.kind==="string"||o.kind==="int")&&t.return&&(g(String(T??"")),b(!0),u(null))});let V=Math.max(...f.map(i=>v(l.config.fields[i.labelKey])))+4;return x(p,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(d,{bold:!0,color:a.accent,children:l.config.title}),c(p,{marginTop:1,flexDirection:"column",flexGrow:1,children:f.map((i,t)=>{let n=t===m,D=C(e,i.path),B=j(i,D,n&&h?w:null,l,i.path==="defaultDict"?y:""),E=l.config.fields[i.labelKey],z=" ".repeat(Math.max(0,V-v(E)));return x(p,{children:[c(d,{color:n?a.accent:a.muted,children:n?"\u258C ":" "}),x(d,{bold:n,color:n?a.text:a.muted,children:[E,z]}),c(d,{color:n?a.accent:a.muted,children:B})]},i.path)})}),A&&c(p,{marginTop:1,children:x(d,{color:a.error,children:["! ",A]})}),c(p,{marginTop:1,children:c(d,{color:a.muted,children:L(o,h,l)})})]})}function j(s,e,r,l,y){if(r!==null)return`${r}_`;if(s.kind==="bool")return e?`\u2713 ${l.common.on}`:`\u2717 ${l.common.off}`;if(s.kind==="dictRef")return e?P(y||String(e),24):"\u2014";if(s.kind==="enum"){if(s.path==="stealth"){let m=String(e);return`< ${l.config.enumValues.stealth[m]??String(e)} >`}return`< ${e} >`}return String(e??"")}function L(s,e,r){return e?r.config.hints.editing:s.kind==="bool"?r.config.hints.bool:s.kind==="enum"?r.config.hints.enum:s.kind==="dictRef"?r.config.hints.dictRef:r.config.hints.stringOrInt}export{U as ConfigEditor};
2
- //# sourceMappingURL=ConfigEditor-U4J6FVDN.js.map
1
+ import{b as M}from"./chunk-2GTGXODM.js";import{d as F}from"./chunk-ELWVQGDK.js";import{b as N,c as v,e as P}from"./chunk-IUFBN3RD.js";import{b as R,d as $,f as a}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useState as S}from"react";import{Box as p,Text as d,useInput as I}from"ink";import{jsx as c,jsxs as x}from"react/jsx-runtime";var f=[{kind:"dictRef",path:"defaultDict",labelKey:"defaultDict"},{kind:"enum",path:"defaultMode",labelKey:"defaultMode",options:["order","dictation","review","random","loop"]},{kind:"enum",path:"accent",labelKey:"accent",options:["us","uk"]},{kind:"enum",path:"language",labelKey:"language",options:["auto","zh","en"]},{kind:"enum",path:"mirror",labelKey:"mirror",options:["jsdelivr","github"]},{kind:"enum",path:"stealth",labelKey:"stealth",options:["off","menu","default"]},{kind:"int",path:"chapterSize",labelKey:"chapterSize",min:1,max:200},{kind:"bool",path:"autoplayPronunciation",labelKey:"autoplayPronunciation"},{kind:"bool",path:"sounds.master",labelKey:"soundsMaster"},{kind:"bool",path:"sounds.keystroke",labelKey:"soundsKeystroke"},{kind:"bool",path:"sounds.feedback",labelKey:"soundsFeedback"},{kind:"string",path:"sounds.keySoundName",labelKey:"soundsKeySound"}];function C(s,e){return e.split(".").reduce((r,l)=>{if(r&&typeof r=="object")return r[l]},s)}function U(){let s=R(),{cfg:e,setCfg:r}=M(),l=$(),y=N(e.defaultDict),[m,K]=S(0),[h,b]=S(!1),[w,g]=S(""),[A,u]=S(null),o=f[m],T=C(e,o.path),k=async i=>{try{let t=F(e,o.path,i);await r(t),b(!1),u(null)}catch(t){u(t.message)}};I((i,t)=>{if(h&&o.kind==="string"){if(t.escape){b(!1),u(null);return}if(t.return){k(w);return}if(t.backspace||t.delete){g(n=>n.slice(0,-1));return}i&&!t.ctrl&&!t.meta&&g(n=>n+i);return}if(h&&o.kind==="int"){if(t.escape){b(!1),u(null);return}if(t.return){k(w);return}if(t.backspace||t.delete){g(n=>n.slice(0,-1));return}/^[0-9]$/.test(i)&&g(n=>n+i);return}if(t.escape){s.back();return}if(t.upArrow){K(n=>(n-1+f.length)%f.length),u(null);return}if(t.downArrow){K(n=>(n+1)%f.length),u(null);return}if(o.kind==="bool"&&(i===" "||t.return)){k(T?"false":"true");return}if(o.kind==="enum"&&(t.leftArrow||t.rightArrow)){let n=o.options.indexOf(String(T)),D=t.rightArrow?1:-1,B=o.options[(n+D+o.options.length)%o.options.length];k(B);return}if(o.kind==="dictRef"&&t.return){s.navigate({name:"dict",params:{pickerMode:"set-default"}});return}(o.kind==="string"||o.kind==="int")&&t.return&&(g(String(T??"")),b(!0),u(null))});let V=Math.max(...f.map(i=>v(l.config.fields[i.labelKey])))+4;return x(p,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(d,{bold:!0,color:a.accent,children:l.config.title}),c(p,{marginTop:1,flexDirection:"column",flexGrow:1,children:f.map((i,t)=>{let n=t===m,D=C(e,i.path),B=j(i,D,n&&h?w:null,l,i.path==="defaultDict"?y:""),E=l.config.fields[i.labelKey],z=" ".repeat(Math.max(0,V-v(E)));return x(p,{children:[c(d,{color:n?a.accent:a.muted,children:n?"\u258C ":" "}),x(d,{bold:n,color:n?a.text:a.muted,children:[E,z]}),c(d,{color:n?a.accent:a.muted,children:B})]},i.path)})}),A&&c(p,{marginTop:1,children:x(d,{color:a.error,children:["! ",A]})}),c(p,{marginTop:1,children:c(d,{color:a.muted,children:L(o,h,l)})})]})}function j(s,e,r,l,y){if(r!==null)return`${r}_`;if(s.kind==="bool")return e?`\u2713 ${l.common.on}`:`\u2717 ${l.common.off}`;if(s.kind==="dictRef")return e?P(y||String(e),24):"\u2014";if(s.kind==="enum"){if(s.path==="stealth"){let m=String(e);return`< ${l.config.enumValues.stealth[m]??String(e)} >`}return`< ${e} >`}return String(e??"")}function L(s,e,r){return e?r.config.hints.editing:s.kind==="bool"?r.config.hints.bool:s.kind==="enum"?r.config.hints.enum:s.kind==="dictRef"?r.config.hints.dictRef:r.config.hints.stringOrInt}export{U as ConfigEditor};
2
+ //# sourceMappingURL=ConfigEditor-HAP5C2Y7.js.map
@@ -1,2 +1,2 @@
1
- import{b as z}from"./chunk-2GTGXODM.js";import{a as F,d as H,f as N}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{b as q,d as J,f as e}from"./chunk-SSDQJ6MT.js";import{b as Y,c as _}from"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useEffect as at,useMemo as dt,useState as b}from"react";import{Box as l,Text as c,useInput as st,useStdout as mt}from"ink";import{useState as lt}from"react";import{Box as k,Text as M,useInput as ct}from"ink";import{jsx as v,jsxs as K}from"react/jsx-runtime";function j({title:D,items:g,onClose:T}){let f=g.map((s,m)=>s.disabled?-1:m).filter(s=>s>=0),o=f[0]??0,[B,P]=lt(o);ct((s,m)=>{if(m.escape){T();return}if(m.upArrow){let a=f.indexOf(B),d=f[(a-1+f.length)%f.length];d!==void 0&&P(d);return}if(m.downArrow){let a=f.indexOf(B),d=f[(a+1)%f.length];d!==void 0&&P(d);return}if(m.return){let a=g[B];a&&!a.disabled&&a.run();return}for(let a=0;a<g.length;a++){let d=g[a];if(!d.disabled&&d.key&&s===d.key){d.run();return}}});let I=Math.max(...g.map(s=>s.label.length)),L=Math.max(I+8,D.length+4,24);return K(k,{flexDirection:"column",borderStyle:"round",borderColor:e.accent,paddingX:2,paddingY:1,width:L,children:[v(k,{marginBottom:1,children:v(M,{bold:!0,color:e.accent,children:D})}),g.map((s,m)=>{let a=m===B,d=s.disabled?e.muted:a?e.text:e.muted;return K(k,{children:[v(M,{color:a?e.accent:e.muted,children:a?"\u258C ":" "}),v(M,{bold:a,color:d,children:s.label})]},m)}),v(k,{marginTop:1,children:v(M,{color:e.muted,children:"\u2191/\u2193 \xB7 Enter \xB7 Esc"})})]})}import{Fragment as ut,jsx as t,jsxs as h}from"react/jsx-runtime";function Mt({params:D}){let g=q(),{cfg:T,setCfg:f}=z(),o=J(),{stdout:B}=mt(),[P,I]=b([]),[L,s]=b(!0),[m,a]=b(0),[d,G]=b(""),[x,y]=b(null),[Q,S]=b(0),[E,p]=b(null),U=async()=>{let n=await _(),i=await Promise.all(n.map(async u=>({entry:u,local:await H(u.id)})));I(i),s(!1)};at(()=>{U()},[Q]);let w=dt(()=>d?P.filter(n=>Y([n.entry],d).length>0):P,[d,P]),C=Math.max(0,Math.min(w.length-1,m)),r=w[C],V=B?.rows??24,R=Math.max(6,V-8),W=Math.floor(R/2),$=Math.max(0,Math.min(w.length-R,C-W)),Z=Math.min(w.length,$+R),O=n=>{g.replace({name:"practice",params:{dictId:n,chapterIndex:0,mode:T.defaultMode,stealth:T.stealth==="default"}})},tt=async(n,i=!0)=>{await f({...T,defaultDict:n}),p(null),i&&(D?.pickerMode==="choose-then-practice"?O(n):g.back())},et=n=>{p(null),y({kind:"removing",id:n}),(async()=>{try{await N(n),y(null),S(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},nt=n=>{p(null),y({kind:"pulling",id:n}),(async()=>{try{await F(n),y(null),S(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},ot=()=>{p(null),y({kind:"refreshing"}),S(n=>n+1),y(null)};if(st((n,i)=>{if(E===null){if(i.escape){g.back();return}if(i.upArrow){a(u=>Math.max(0,u-1));return}if(i.downArrow){a(u=>Math.min(w.length-1,u+1));return}if(i.ctrl&&n==="k"){p("more");return}if(i.return){r&&p("item");return}if(i.backspace||i.delete){G(u=>u.slice(0,-1)),a(0);return}n&&!i.ctrl&&!i.meta&&n.length===1&&(G(u=>u+n),a(0))}}),L)return t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(c,{color:e.muted,children:o.dict.loading})});let rt=r?[{label:o.dict.action.setDefault,run:()=>{tt(r.entry.id,D?.pickerMode!==void 0)}},{label:o.dict.action.practice,run:()=>O(r.entry.id)},{label:o.dict.action.delete,disabled:!r.local,run:()=>et(r.entry.id)},{label:o.common.cancel,run:()=>p(null)}]:[],it=[{label:o.dict.command.pull,disabled:!r,run:()=>r&&nt(r.entry.id)},{label:o.dict.command.import,disabled:!0,run:()=>{}},{label:o.dict.command.refreshList,run:()=>ot()},{label:o.common.cancel,run:()=>p(null)}];return E==="item"&&r?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(j,{title:`${o.dict.action.title} \xB7 ${r.entry.name}`,items:rt,onClose:()=>p(null)})}):E==="more"?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(j,{title:o.dict.command.title,items:it,onClose:()=>p(null)})}):h(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[h(l,{children:[t(c,{bold:!0,color:e.accent,children:o.dict.title}),t(l,{flexGrow:1}),t(c,{color:e.muted,children:d?`${o.dict.filterPlaceholder}: ${d}_`:`${o.dict.filterPlaceholder}_`}),h(c,{color:e.muted,children:[" ",o.dict.entries(w.length)]})]}),h(l,{marginTop:1,flexGrow:1,children:[t(l,{flexDirection:"column",width:"75%",paddingRight:1,children:w.slice($,Z).map((n,i)=>{let A=$+i===C,X=T.defaultDict===n.entry.id;return h(l,{children:[t(l,{width:2,children:t(c,{color:A?e.accent:e.muted,children:A?"\u258C ":" "})}),t(l,{width:2,children:t(c,{color:n.local?e.accent:e.muted,children:n.local?"\u25CF":"\u25CB"})}),t(l,{width:2,children:t(c,{color:X?e.success:e.muted,children:X?"\u2605":" "})}),t(l,{flexGrow:1,children:t(c,{bold:A,color:A?e.text:e.muted,wrap:"truncate",children:n.entry.name})}),t(l,{width:6,children:t(c,{color:e.muted,children:String(n.entry.length).padStart(5)})})]},n.entry.id)})}),t(l,{flexDirection:"column",width:"25%",paddingLeft:1,children:r&&h(ut,{children:[t(c,{bold:!0,color:e.text,wrap:"wrap",children:r.entry.name}),t(c,{color:e.muted,children:r.entry.id}),t(l,{marginTop:1,children:h(c,{color:e.muted,wrap:"wrap",children:[r.entry.language," \xB7 ",r.entry.category]})}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.wordsLabel(r.entry.length)})}),r.entry.description&&t(l,{marginTop:1,children:t(c,{color:e.primary,wrap:"wrap",children:r.entry.description})}),r.entry.tags.length>0&&t(l,{marginTop:1,children:t(c,{color:e.muted,wrap:"wrap",children:o.dict.tagsLabel(r.entry.tags.join(", "))})}),t(l,{marginTop:1,children:t(c,{color:r.local?e.accent:e.muted,children:r.local?o.dict.local:o.dict.notLocal})}),T.defaultDict===r.entry.id&&t(l,{children:t(c,{color:e.success,children:o.dict.defaultMark})})]})})]}),x&&h(l,{marginTop:1,children:[x.kind==="pulling"&&t(c,{color:e.warning,children:o.dict.pulling(x.id)}),x.kind==="removing"&&t(c,{color:e.warning,children:o.dict.removing(x.id)}),x.kind==="refreshing"&&h(c,{color:e.warning,children:[o.dict.command.refreshList,"\u2026"]}),x.kind==="error"&&t(c,{color:e.error,children:o.dict.errorOn(x.id,x.msg)})]}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.footer})})]})}export{Mt as DictBrowser};
2
- //# sourceMappingURL=DictBrowser-DZZWZXEA.js.map
1
+ import{a as F,d as H,f as N}from"./chunk-TP77EGJ2.js";import{b as z}from"./chunk-2GTGXODM.js";import"./chunk-ELWVQGDK.js";import{b as q,d as J,f as e}from"./chunk-R6HQWKXU.js";import{b as Y,c as _}from"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useEffect as at,useMemo as dt,useState as b}from"react";import{Box as l,Text as c,useInput as st,useStdout as mt}from"ink";import{useState as lt}from"react";import{Box as k,Text as M,useInput as ct}from"ink";import{jsx as v,jsxs as K}from"react/jsx-runtime";function j({title:D,items:g,onClose:T}){let f=g.map((s,m)=>s.disabled?-1:m).filter(s=>s>=0),o=f[0]??0,[B,P]=lt(o);ct((s,m)=>{if(m.escape){T();return}if(m.upArrow){let a=f.indexOf(B),d=f[(a-1+f.length)%f.length];d!==void 0&&P(d);return}if(m.downArrow){let a=f.indexOf(B),d=f[(a+1)%f.length];d!==void 0&&P(d);return}if(m.return){let a=g[B];a&&!a.disabled&&a.run();return}for(let a=0;a<g.length;a++){let d=g[a];if(!d.disabled&&d.key&&s===d.key){d.run();return}}});let I=Math.max(...g.map(s=>s.label.length)),L=Math.max(I+8,D.length+4,24);return K(k,{flexDirection:"column",borderStyle:"round",borderColor:e.accent,paddingX:2,paddingY:1,width:L,children:[v(k,{marginBottom:1,children:v(M,{bold:!0,color:e.accent,children:D})}),g.map((s,m)=>{let a=m===B,d=s.disabled?e.muted:a?e.text:e.muted;return K(k,{children:[v(M,{color:a?e.accent:e.muted,children:a?"\u258C ":" "}),v(M,{bold:a,color:d,children:s.label})]},m)}),v(k,{marginTop:1,children:v(M,{color:e.muted,children:"\u2191/\u2193 \xB7 Enter \xB7 Esc"})})]})}import{Fragment as ut,jsx as t,jsxs as h}from"react/jsx-runtime";function Mt({params:D}){let g=q(),{cfg:T,setCfg:f}=z(),o=J(),{stdout:B}=mt(),[P,I]=b([]),[L,s]=b(!0),[m,a]=b(0),[d,G]=b(""),[x,y]=b(null),[Q,S]=b(0),[E,p]=b(null),U=async()=>{let n=await _(),i=await Promise.all(n.map(async u=>({entry:u,local:await H(u.id)})));I(i),s(!1)};at(()=>{U()},[Q]);let w=dt(()=>d?P.filter(n=>Y([n.entry],d).length>0):P,[d,P]),C=Math.max(0,Math.min(w.length-1,m)),r=w[C],V=B?.rows??24,R=Math.max(6,V-8),W=Math.floor(R/2),$=Math.max(0,Math.min(w.length-R,C-W)),Z=Math.min(w.length,$+R),O=n=>{g.replace({name:"practice",params:{dictId:n,chapterIndex:0,mode:T.defaultMode,stealth:T.stealth==="default"}})},tt=async(n,i=!0)=>{await f({...T,defaultDict:n}),p(null),i&&(D?.pickerMode==="choose-then-practice"?O(n):g.back())},et=n=>{p(null),y({kind:"removing",id:n}),(async()=>{try{await N(n),y(null),S(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},nt=n=>{p(null),y({kind:"pulling",id:n}),(async()=>{try{await F(n),y(null),S(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},ot=()=>{p(null),y({kind:"refreshing"}),S(n=>n+1),y(null)};if(st((n,i)=>{if(E===null){if(i.escape){g.back();return}if(i.upArrow){a(u=>Math.max(0,u-1));return}if(i.downArrow){a(u=>Math.min(w.length-1,u+1));return}if(i.ctrl&&n==="k"){p("more");return}if(i.return){r&&p("item");return}if(i.backspace||i.delete){G(u=>u.slice(0,-1)),a(0);return}n&&!i.ctrl&&!i.meta&&n.length===1&&(G(u=>u+n),a(0))}}),L)return t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(c,{color:e.muted,children:o.dict.loading})});let rt=r?[{label:o.dict.action.setDefault,run:()=>{tt(r.entry.id,D?.pickerMode!==void 0)}},{label:o.dict.action.practice,run:()=>O(r.entry.id)},{label:o.dict.action.delete,disabled:!r.local,run:()=>et(r.entry.id)},{label:o.common.cancel,run:()=>p(null)}]:[],it=[{label:o.dict.command.pull,disabled:!r,run:()=>r&&nt(r.entry.id)},{label:o.dict.command.import,disabled:!0,run:()=>{}},{label:o.dict.command.refreshList,run:()=>ot()},{label:o.common.cancel,run:()=>p(null)}];return E==="item"&&r?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(j,{title:`${o.dict.action.title} \xB7 ${r.entry.name}`,items:rt,onClose:()=>p(null)})}):E==="more"?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(j,{title:o.dict.command.title,items:it,onClose:()=>p(null)})}):h(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[h(l,{children:[t(c,{bold:!0,color:e.accent,children:o.dict.title}),t(l,{flexGrow:1}),t(c,{color:e.muted,children:d?`${o.dict.filterPlaceholder}: ${d}_`:`${o.dict.filterPlaceholder}_`}),h(c,{color:e.muted,children:[" ",o.dict.entries(w.length)]})]}),h(l,{marginTop:1,flexGrow:1,children:[t(l,{flexDirection:"column",width:"75%",paddingRight:1,children:w.slice($,Z).map((n,i)=>{let A=$+i===C,X=T.defaultDict===n.entry.id;return h(l,{children:[t(l,{width:2,children:t(c,{color:A?e.accent:e.muted,children:A?"\u258C ":" "})}),t(l,{width:2,children:t(c,{color:n.local?e.accent:e.muted,children:n.local?"\u25CF":"\u25CB"})}),t(l,{width:2,children:t(c,{color:X?e.success:e.muted,children:X?"\u2605":" "})}),t(l,{flexGrow:1,children:t(c,{bold:A,color:A?e.text:e.muted,wrap:"truncate",children:n.entry.name})}),t(l,{width:6,children:t(c,{color:e.muted,children:String(n.entry.length).padStart(5)})})]},n.entry.id)})}),t(l,{flexDirection:"column",width:"25%",paddingLeft:1,children:r&&h(ut,{children:[t(c,{bold:!0,color:e.text,wrap:"wrap",children:r.entry.name}),t(c,{color:e.muted,children:r.entry.id}),t(l,{marginTop:1,children:h(c,{color:e.muted,wrap:"wrap",children:[r.entry.language," \xB7 ",r.entry.category]})}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.wordsLabel(r.entry.length)})}),r.entry.description&&t(l,{marginTop:1,children:t(c,{color:e.primary,wrap:"wrap",children:r.entry.description})}),r.entry.tags.length>0&&t(l,{marginTop:1,children:t(c,{color:e.muted,wrap:"wrap",children:o.dict.tagsLabel(r.entry.tags.join(", "))})}),t(l,{marginTop:1,children:t(c,{color:r.local?e.accent:e.muted,children:r.local?o.dict.local:o.dict.notLocal})}),T.defaultDict===r.entry.id&&t(l,{children:t(c,{color:e.success,children:o.dict.defaultMark})})]})})]}),x&&h(l,{marginTop:1,children:[x.kind==="pulling"&&t(c,{color:e.warning,children:o.dict.pulling(x.id)}),x.kind==="removing"&&t(c,{color:e.warning,children:o.dict.removing(x.id)}),x.kind==="refreshing"&&h(c,{color:e.warning,children:[o.dict.command.refreshList,"\u2026"]}),x.kind==="error"&&t(c,{color:e.error,children:o.dict.errorOn(x.id,x.msg)})]}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.footer})})]})}export{Mt as DictBrowser};
2
+ //# sourceMappingURL=DictBrowser-7CIISVDN.js.map
@@ -1,2 +1,2 @@
1
- import{b as p,d as a,f as i}from"./chunk-SSDQJ6MT.js";import{Box as l,Text as n,useInput as x}from"ink";import{jsx as o,jsxs as c}from"react/jsx-runtime";function f(){let m=p(),t=a();x((s,r)=>{r.escape&&m.back()});let e=t.help.keys,u=[{title:t.help.sections.global,keys:[e.helpScreen,e.quit]},{title:t.help.sections.main,keys:[e.navigate,e.select,e.letterJump,e.helpScreen]},{title:t.help.sections.practice,keys:[e.pause,e.skip,e.replay,e.resume,e.nextChapter,e.reviewMistakes,e.stealthToggle,e.backMenu]},{title:t.help.sections.dict,keys:[e.navigate,e.filter,e.itemActions,e.moreActions,e.backScreen]},{title:t.help.sections.config,keys:[e.navigate,e.select,e.backMenu]},{title:t.help.sections.stats,keys:[e.cycleWindow,e.backMenu]},{title:t.help.sections.word,keys:[e.filter,e.navigate,e.backMenu]}];return c(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(l,{children:[o(n,{bold:!0,color:i.accent,children:t.help.title}),o(n,{color:i.muted,children:" \xB7 "}),o(n,{color:i.muted,children:t.help.subtitle})]}),o(l,{marginTop:1,flexDirection:"column",flexGrow:1,children:u.map(s=>c(l,{flexDirection:"column",marginTop:1,children:[o(n,{bold:!0,color:i.text,children:s.title}),s.keys.map((r,k)=>o(l,{children:c(n,{color:i.muted,children:[" \xB7 ",r]})},k))]},s.title))}),o(l,{marginTop:1,children:o(n,{color:i.muted,children:t.help.footer})})]})}export{f as HelpScreen};
2
- //# sourceMappingURL=HelpScreen-7U5XF3IU.js.map
1
+ import{b as p,d as a,f as i}from"./chunk-R6HQWKXU.js";import{Box as l,Text as n,useInput as x}from"ink";import{jsx as o,jsxs as c}from"react/jsx-runtime";function f(){let m=p(),t=a();x((s,r)=>{r.escape&&m.back()});let e=t.help.keys,u=[{title:t.help.sections.global,keys:[e.helpScreen,e.quit]},{title:t.help.sections.main,keys:[e.navigate,e.select,e.letterJump,e.helpScreen]},{title:t.help.sections.practice,keys:[e.pause,e.skip,e.replay,e.resume,e.nextChapter,e.reviewMistakes,e.stealthToggle,e.backMenu]},{title:t.help.sections.dict,keys:[e.navigate,e.filter,e.itemActions,e.moreActions,e.backScreen]},{title:t.help.sections.config,keys:[e.navigate,e.select,e.backMenu]},{title:t.help.sections.stats,keys:[e.cycleWindow,e.backMenu]},{title:t.help.sections.word,keys:[e.filter,e.navigate,e.backMenu]}];return c(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(l,{children:[o(n,{bold:!0,color:i.accent,children:t.help.title}),o(n,{color:i.muted,children:" \xB7 "}),o(n,{color:i.muted,children:t.help.subtitle})]}),o(l,{marginTop:1,flexDirection:"column",flexGrow:1,children:u.map(s=>c(l,{flexDirection:"column",marginTop:1,children:[o(n,{bold:!0,color:i.text,children:s.title}),s.keys.map((r,k)=>o(l,{children:c(n,{color:i.muted,children:[" \xB7 ",r]})},k))]},s.title))}),o(l,{marginTop:1,children:o(n,{color:i.muted,children:t.help.footer})})]})}export{f as HelpScreen};
2
+ //# sourceMappingURL=HelpScreen-P4U5O4OP.js.map
@@ -0,0 +1,2 @@
1
+ import{b as pt,d as wt}from"./chunk-7RMRK5MO.js";import{a as st,b as ut,c as lt,d as dt,e as mt,f as ft}from"./chunk-KBRGNL2D.js";import{e as at}from"./chunk-TP77EGJ2.js";import{b as Y}from"./chunk-2GTGXODM.js";import"./chunk-ELWVQGDK.js";import{a as yt}from"./chunk-MPE25TTQ.js";import{a as V,b as xt,c as bt}from"./chunk-UPA4JFCH.js";import{b as ht,e as L}from"./chunk-IUFBN3RD.js";import{b as X,d as M,f as i,g as gt}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useState as j,useEffect as H,useRef as Z}from"react";import{Box as f,Text as h,useApp as ue,useInput as K}from"ink";function Tt(t,e=Math.random){let r=[...t];for(let n=r.length-1;n>0;n--){let c=Math.floor(e()*(n+1)),l=r[n];r[n]=r[c],r[c]=l}return r}function It(t){let e=t>>>0;return()=>{e=e+1831565813>>>0;let r=Math.imul(e^e>>>15,1|e);return r=r+Math.imul(r^r>>>7,61|r)^r,((r^r>>>14)>>>0)/4294967296}}function St(t,e){if(e<=0)throw new Error("chapterSize must be positive");let r=[];for(let n=0;n<t.length;n+=e)r.push(t.slice(n,n+e));return r}function kt(t,e,r){if(e==="random"){let n=r===void 0?Math.random:It(r);return Tt(t,n)}return t}function _(t){return{target:t,typed:"",errorsThisWord:0}}function Ct(t,e){switch(e.type){case"reset":return{state:{...t,typed:""},effect:"none"};case"backspace":return t.typed.length===0?{state:t,effect:"none"}:{state:{...t,typed:t.typed.slice(0,-1)},effect:"none"};case"char":{let r=t.typed+e.ch,n=[...t.target].slice(0,[...r].length).join("");return r===n?r.length===t.target.length?{state:{...t,typed:r},effect:"correct"}:{state:{...t,typed:r},effect:"progress"}:{state:{...t,typed:"",errorsThisWord:t.errorsThisWord+1},effect:"wrong"}}}}function q(t,e=Date.now()){return t.length===0?{startedAt:e,results:[],current:null,finishedAt:e,playlist:t}:{startedAt:e,results:[],current:{wordIndex:0,wordStartedAt:e,input:_(t[0].name)},finishedAt:null,playlist:t}}function J(t,e,r=Date.now()){if(!t.current)return{session:t,effect:"none"};let{state:n,effect:c}=Ct(t.current.input,e);if(c==="correct"){let l={word:n.target,errors:n.errorsThisWord,durationMs:r-t.current.wordStartedAt},a=t.current.wordIndex+1,s=[...t.results,l];return a>=t.playlist.length?{session:{...t,results:s,current:null,finishedAt:r},effect:c}:{session:{...t,results:s,current:{wordIndex:a,wordStartedAt:r,input:_(t.playlist[a].name)}},effect:c}}return{session:{...t,current:{...t.current,input:n}},effect:c}}function Mt(t,e=Date.now()){if(!t.current)return{session:t,effect:"none"};let r={word:t.current.input.target,errors:0,durationMs:e-t.current.wordStartedAt,skipped:!0},n=t.current.wordIndex+1,c=[...t.results,r];return n>=t.playlist.length?{session:{...t,results:c,current:null,finishedAt:e},effect:"skipped"}:{session:{...t,results:c,current:{wordIndex:n,wordStartedAt:e,input:_(t.playlist[n].name)}},effect:"skipped"}}function Q(t){let e=t.results.reduce((c,l)=>c+l.errors,0),r=(t.finishedAt??Date.now())-t.startedAt,n={};for(let c of t.results)c.errors>0&&(n[c.word]=(n[c.word]??0)+c.errors);return{wordCount:t.results.length,errors:e,durationMs:r,perWordErrors:n}}import{useEffect as Wt,useReducer as qt,useRef as Jt,useState as Qt}from"react";import{useInput as Zt,useApp as te}from"ink";function ee(t,e){if(e.type==="start")return{session:q(e.playlist,e.now),lastEffect:null};if(e.type==="skip"){let r=Mt(t.session,e.now);return{session:r.session,lastEffect:r.effect}}if(e.type==="event"){if(e.key.backspace||e.key.delete){let c=J(t.session,{type:"backspace"},e.now);return{session:c.session,lastEffect:c.effect}}if(e.input.length===0)return t;let r=t.session,n=t.lastEffect;for(let c of e.input){let l=J(r,{type:"char",ch:c},e.now);if(r=l.session,n=l.effect,r.finishedAt!==null)break}return{session:r,lastEffect:n}}return t}function re(t){let e=[...t];if(e.some(n=>n.codePointAt(0)>=128))return{kind:"ime",cleaned:""};let r=e.filter(n=>{let c=n.codePointAt(0);return c>=32&&c<=126}).join("");return{kind:r.length>0?"valid":"noise",cleaned:r}}function vt({playlist:t,onComplete:e,onTab:r,onEscape:n,onSkip:c,onImeBlock:l,onValidInput:a,enabled:s=!0}){let[d,b]=qt(ee,void 0,()=>({session:q(t,Date.now()),lastEffect:null})),W=Jt(!1),[I,S]=Qt(0),{exit:w}=te();return Zt((p,x)=>{if(x.ctrl&&p==="c"){w();return}if(x.ctrl&&p==="n"){c?.(),b({type:"skip",now:Date.now()});return}if(x.escape){n?.();return}if(x.tab){r?.();return}if(x.upArrow||x.downArrow||x.leftArrow||x.rightArrow||x.return||x.ctrl||x.meta)return;let{kind:v,cleaned:R}=re(p);if(v==="ime"){l?.();return}v!=="noise"&&(a?.(),b({type:"event",input:R,key:x,now:Date.now()}))},{isActive:s}),Wt(()=>{d.session.finishedAt!==null&&!W.current&&(W.current=!0,e(d.session))},[d.session,e]),Wt(()=>{if(d.session.finishedAt!==null)return;let p=setInterval(()=>S(x=>x+1),1e3);return()=>clearInterval(p)},[d.session.finishedAt]),{session:d.session,lastEffect:d.lastEffect,tick:I}}import{useEffect as ne,useRef as oe}from"react";function Et(t){let e=oe(!1);return ne(()=>{e.current||(e.current=!0,st(!t.enabled).catch(()=>{}))},[t.enabled]),{keystroke:()=>t.enabled&&ut(),correct:()=>t.enabled&&lt(),wrong:()=>t.enabled&&dt(),pronounce:r=>{t.enabled&&t.autoplayPronunciation&&mt(r,t.accent)},prefetch:r=>{t.enabled&&ft(r,t.accent)}}}import{useCallback as ce}from"react";function Bt(t){return ce(async e=>{let r={ts:new Date().toISOString(),dictId:t.dictId,chapter:t.chapterIndex,mode:t.mode,wordCount:e.wordCount,errors:e.errors,durationMs:e.durationMs,perWordErrors:e.perWordErrors};await yt(r),wt({dictId:t.dictId,chapterIndex:t.chapterIndex,mode:t.mode,wordCount:e.wordCount,errors:e.errors,durationMs:e.durationMs,perWordErrors:e.perWordErrors});let n=Object.entries(e.perWordErrors).filter(([,l])=>l>0);if(n.length===0)return;let c=await V();for(let[l,a]of n)c=bt(c,l,t.dictId,a);await xt(c)},[t.dictId,t.chapterIndex,t.mode])}import{Box as N,Text as m,useStdout as ie}from"ink";import{Fragment as se,jsx as u,jsxs as A}from"react/jsx-runtime";var At=28;function Pt(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function ae(){let{stdout:t}=ie(),e=t?.columns??80;return Math.max(20,e-At)}function B({left:t,right:e}){let r=ae();return A(N,{children:[u(N,{width:r,children:t}),u(N,{width:At,justifyContent:"flex-end",children:e})]})}function $t(t){let e=M(),r=[...t.target],n=[...t.typed],c=A(N,{children:[r.map((S,w)=>{let p=w<n.length,x=t.hideTarget&&!p?"_":p?n[w]:S,v=t.error?i.error:p?i.accent:i.muted;return u(m,{bold:!0,color:v,children:x},w)}),t.phonetic&&A(se,{children:[u(m,{children:" "}),u(m,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})]})]}),l=t.translation.length>0?u(m,{color:i.primary,children:t.translation[0]}):u(m,{children:" "}),a=t.info,s=Number.isInteger(a.accPct)?`${a.accPct}`:a.accPct.toFixed(1),d=!t.imeBlocked&&t.audioWarning!==null,b=t.imeBlocked?u(m,{color:i.warning,children:e.practice.imeWarningShort}):d?u(m,{color:i.warning,children:e.practice.audioWarningShort}):a.visible?u(m,{color:i.muted,children:`${a.dictName} \xB7 ${a.chapterLabel}`}):u(m,{children:" "}),W=t.imeBlocked||d?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:`${a.completed}/${a.total} \xB7 ${a.wpm}wpm \xB7 ${s}%`}):u(m,{children:" "}),I=t.imeBlocked||d?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:Pt(a.elapsedMs)}):u(m,{children:" "});return A(N,{flexDirection:"column",children:[u(B,{left:c,right:b}),u(B,{left:u(m,{children:" "}),right:W}),u(B,{left:l,right:I})]})}function Nt(){let t=M();return A(N,{flexDirection:"column",children:[u(B,{left:u(m,{color:i.warning,children:t.stealth.paused}),right:u(m,{color:i.muted,children:t.stealth.pausedHintRight})}),u(B,{left:u(m,{children:" "}),right:A(m,{color:i.muted,children:["Esc ",t.common.back]})}),u(B,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}function Rt(t){let e=M(),r=Number.isInteger(t.accPct)?`${t.accPct}`:t.accPct.toFixed(1),n=`${e.stealth.chapterDone} \xB7 ${t.wordCount}w \xB7 ${t.wpm}wpm \xB7 ${r}% \xB7 ${Pt(t.durationMs)}`;return A(N,{flexDirection:"column",children:[u(B,{left:u(m,{color:i.success,children:n}),right:u(m,{color:i.muted,children:e.stealth.nextHintRight})}),u(B,{left:u(m,{children:" "}),right:A(m,{color:i.muted,children:["Esc ",e.common.back]})}),u(B,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}import{jsx as o,jsxs as C}from"react/jsx-runtime";function or({params:t}){let{dictId:e,chapterIndex:r,mode:n}=t,{cfg:c}=Y(),l=M(),[a,s]=j("loading"),[d,b]=j(null),[W,I]=j(null);return H(()=>{let S=!1;return s("loading"),b(null),I(null),(async()=>{try{let w=await at(e);if(S)return;if(n==="review"){let R=await V();if(S)return;let O=w.filter(F=>R[F.name]?.count).slice(0,c.chapterSize);if(O.length===0){I(l.practice.errors.noMistakes),s("error");return}b({playlist:O,totalChapters:1}),s("typing");return}let p=St(w,c.chapterSize);if(p.length===0){I(l.practice.errors.dictEmpty(e)),s("error");return}let x=Math.max(0,Math.min(p.length-1,r)),v=kt(p[x],n);b({playlist:v,totalChapters:p.length}),s("typing")}catch(w){if(S)return;I(w.message),s("error")}})(),()=>{S=!0}},[e,r,n,c.chapterSize,l]),a==="loading"?o(ge,{text:l.practice.loading,color:i.muted}):a==="error"?o(pe,{msg:W??l.practice.errors.unknown}):d?o(le,{params:t,loaded:d,phase:a,setPhase:s},`${e}-${r}-${n}-${t.stealth?"s":"n"}`):null}function le({params:t,loaded:e,phase:r,setPhase:n}){let{dictId:c,chapterIndex:l,mode:a}=t,s=t.stealth===!0,{cfg:d}=Y(),b=X(),{exit:W}=ue(),I=()=>b.stack.length>1?b.back():W(),S=Bt({dictId:c,chapterIndex:l,mode:a}),w=ht(c),p=Et({enabled:!s&&d.sounds.master,accent:d.accent,autoplayPronunciation:!s&&d.autoplayPronunciation}),x=pt(),v=d.sounds.master?x.warning:null,R=Z(!1),O=Z(null),F=Z(-1),[jt,et]=j(!1),[rt,Ot]=j(null),[nt,ot]=j(!1);H(()=>{if(rt===null)return;let g=setTimeout(()=>et(!1),2e3);return()=>clearTimeout(g)},[rt]);let{session:T,lastEffect:E,tick:Ft}=vt({playlist:e.playlist,enabled:r==="typing",onComplete:g=>{R.current||(R.current=!0,n("summary"),Promise.resolve(S(Q(g))).catch(y=>{console.error("Failed to persist session:",y)}))},onEscape:()=>n(r==="paused"?"typing":"paused"),onTab:s?void 0:()=>{let g=T.current?e.playlist[T.current.wordIndex]:void 0;g&&p.pronounce(g.name)},onImeBlock:()=>ot(!0),onValidInput:()=>ot(!1)});H(()=>{s||E!==null&&E!==O.current&&(O.current=E,E==="wrong"&&d.sounds.feedback&&p.wrong(),E==="progress"&&d.sounds.keystroke&&p.keystroke(),E==="correct"&&(d.sounds.feedback&&p.correct(),d.sounds.keystroke&&p.keystroke()))},[s,E,p,d.sounds.feedback,d.sounds.keystroke]),H(()=>{if(s)return;let g=T.current?.wordIndex??-1;if(g===-1||g===F.current)return;F.current=g;let y=e.playlist[g],$=e.playlist[g+1];y&&d.autoplayPronunciation&&p.pronounce(y.name),$&&p.prefetch($.name)},[s,T.current?.wordIndex,p,d.autoplayPronunciation,e.playlist]),K((g,y)=>{if(y.tab){et(!0),Ot(Date.now());return}},{isActive:s&&r==="typing"}),K((g,y)=>{if(y.return){n("typing");return}if(y.escape){I();return}},{isActive:r==="paused"}),K((g,y)=>{if(y.escape){I();return}if(y.return){let $=l+1;a==="loop"?b.replace({name:"practice",params:{dictId:c,chapterIndex:l,mode:a,stealth:t.stealth}}):a==="review"||$>=e.totalChapters?I():b.replace({name:"practice",params:{dictId:c,chapterIndex:$,mode:a,stealth:t.stealth}});return}if(g==="m"){b.replace({name:"practice",params:{dictId:c,chapterIndex:0,mode:"review",stealth:t.stealth}});return}},{isActive:r==="summary"});let P=T.results.length,Vt=T.results.reduce((g,y)=>g+y.errors,0),z=Date.now()-T.startedAt,ct=z/6e4,it=ct>0?Math.round(P/ct*10)/10:0,k=r==="summary"?Q(T):null;if(s){if(r==="paused")return o(Nt,{});if(r==="summary"&&k){let D=k.durationMs/6e4,zt=D>0?Math.round(k.wordCount/D*10)/10:0,Ut=Object.keys(k.perWordErrors).length,Xt=k.wordCount===0?1:Math.max(0,(k.wordCount-Ut)/k.wordCount),Yt=Math.round(Xt*1e3)/10;return o(Rt,{wordCount:k.wordCount,errors:k.errors,durationMs:k.durationMs,wpm:zt,accPct:Yt})}let g=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],y=T.current?.input??{target:"",typed:"",errorsThisWord:0},$=new Set(T.results.filter(D=>D.errors>0).map(D=>D.word)).size,Gt=P===0?1:Math.max(0,(P-$)/P),Ht=Math.round(Gt*1e3)/10,Kt=a==="review"?"review":`ch ${l+1}/${e.totalChapters}`;return o($t,{target:g?.name??"",typed:y.typed,hideTarget:a==="dictation",phonetic:Dt(g,d.accent),translation:g?.trans??[],error:E==="wrong",imeBlocked:nt,audioWarning:v,info:{visible:jt,dictName:L(w,24),chapterLabel:Kt,completed:P,total:e.playlist.length,wpm:it,accPct:Ht,elapsedMs:z}})}if(r==="paused")return o(fe,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:a,completed:P,total:e.playlist.length});if(r==="summary"&&k)return o(xe,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:a,summary:k});let U=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],_t=T.current?.input??{target:"",typed:"",errorsThisWord:0};return o(de,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:a,accent:d.accent,completed:P,total:e.playlist.length,errors:Vt,wpm:it,elapsedMs:z,target:U?.name??"",typed:_t.typed,flashError:E==="wrong",hideTarget:a==="dictation",phonetic:Dt(U,d.accent),translation:U?.trans??[],imeBlocked:nt,audioWarning:v})}function Dt(t,e){return t?(e==="us"?t.usphone:t.ukphone)??null:null}function tt(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function de(t){let e=M(),r=t.total===0?0:t.completed/t.total;return C(f,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[o(me,{dictName:t.dictName,chapterIndex:t.chapterIndex,totalChapters:t.totalChapters,mode:t.mode,accent:t.accent,completed:t.completed,total:t.total,elapsedMs:t.elapsedMs}),C(f,{flexGrow:1,flexDirection:"column",alignItems:"center",justifyContent:"center",children:[o(gt,{target:t.target,typed:t.typed,error:t.flashError,hideTarget:t.hideTarget}),t.phonetic&&o(f,{marginTop:1,children:o(h,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})}),t.translation.length>0&&o(f,{marginTop:1,flexDirection:"column",alignItems:"center",children:t.translation.slice(0,2).map((n,c)=>o(h,{color:i.primary,children:n},c))}),t.imeBlocked&&o(f,{marginTop:1,children:o(h,{color:i.warning,children:e.practice.imeWarning})}),!t.imeBlocked&&t.audioWarning&&o(f,{marginTop:1,children:o(h,{color:i.warning,children:t.audioWarning})})]}),C(f,{flexDirection:"column",children:[o(Lt,{frac:r}),o(f,{justifyContent:"center",marginTop:1,children:C(h,{color:i.muted,children:[t.completed,"/",t.total," \xB7 ",tt(t.elapsedMs)," \xB7 ",t.wpm," ",e.practice.statCards.wpm," \xB7 ",t.errors," ",e.practice.statCards.errors]})}),o(f,{justifyContent:"center",marginTop:1,children:o(h,{color:i.muted,children:e.practice.footers.typing})})]})]})}function me(t){let e=M(),r=e.practice.modes[t.mode],n=e.practice.accents[t.accent],c=L(t.dictName,20),l=t.mode==="review"?`${c} \xB7 ${e.practice.reviewLabel} \xB7 ${n}`:`${c} \xB7 ${e.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${r} \xB7 ${n}`,a=`${t.completed}/${t.total} \xB7 ${tt(t.elapsedMs)}`;return C(f,{children:[o(h,{color:i.muted,children:l}),o(f,{flexGrow:1}),o(h,{color:i.muted,children:a})]})}function Lt({frac:t}){let e=process.stdout.columns??80,r=Math.max(20,Math.min(72,e-16)),n=Math.round(r*Math.max(0,Math.min(1,t))),c=r-n;return C(f,{justifyContent:"center",children:[o(h,{color:i.accent,children:"\u2501".repeat(n)}),o(h,{color:i.muted,children:"\u2500".repeat(c)})]})}function fe(t){let e=M(),r=t.total===0?0:t.completed/t.total,n=t.mode==="review"?`${L(t.dictName,20)} \xB7 ${e.practice.reviewLabel}`:`${L(t.dictName,20)} \xB7 ${e.practice.pause.chapter(t.chapterIndex+1,t.totalChapters)}`;return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(h,{bold:!0,color:i.warning,children:e.practice.pause.title}),o(f,{marginTop:1,children:o(h,{color:i.muted,children:n})}),o(f,{marginTop:2,children:o(Lt,{frac:r})}),o(f,{marginTop:1,children:o(h,{color:i.muted,children:e.practice.pause.progress(t.completed,t.total)})}),o(f,{marginTop:2,children:o(h,{color:i.muted,children:e.practice.pause.hint})})]})}function pe({msg:t}){let e=M();return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(h,{color:i.error,children:t}),o(f,{marginTop:2,children:C(h,{color:i.muted,children:["Esc ",e.common.back]})}),o(he,{})]})}function he(){let t=X();return K((e,r)=>{r.escape&&t.back()}),null}function ge({text:t,color:e}){return o(f,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(h,{color:e,children:t})})}function xe(t){let{summary:e}=t,r=e.durationMs/6e4,n=r>0?Math.round(e.wordCount/r*10)/10:0,c=Object.keys(e.perWordErrors).length,l=e.wordCount===0?1:Math.max(0,(e.wordCount-c)/e.wordCount),a=Math.round(l*1e3)/10,s=M(),d=s.practice.modes[t.mode],b=L(t.dictName,20),W=t.mode==="review"?`${b} \xB7 ${s.practice.reviewLabel}`:`${b} \xB7 ${s.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${d}`,S=`Enter ${t.mode==="loop"?s.practice.summary.loopAgain:t.mode==="review"||t.chapterIndex+1>=t.totalChapters?s.practice.summary.backMenu:s.practice.summary.nextChapter} \xB7 m ${s.practice.summary.reviewMistakes} \xB7 Esc ${s.practice.summary.backMenu}`;return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",paddingY:1,width:"100%",height:"100%",children:[o(h,{bold:!0,color:i.success,children:s.practice.chapterComplete}),o(f,{marginTop:1,children:o(h,{color:i.muted,children:W})}),C(f,{marginTop:3,flexDirection:"row",justifyContent:"center",children:[o(G,{label:s.practice.statCards.words,value:String(e.wordCount),color:i.text}),o(G,{label:s.practice.statCards.errors,value:String(e.errors),color:e.errors>0?i.error:i.muted}),o(G,{label:s.practice.statCards.wpm,value:String(n),color:i.accent}),o(G,{label:s.practice.statCards.accuracy,value:`${a}%`,color:i.accent})]}),o(f,{marginTop:2,children:o(h,{color:i.muted,children:s.practice.statCards.elapsed(tt(e.durationMs))})}),o(f,{flexGrow:1}),o(f,{marginTop:2,children:o(h,{color:i.muted,children:S})})]})}function G({label:t,value:e,color:r}){return C(f,{flexDirection:"column",alignItems:"center",marginX:3,children:[o(h,{bold:!0,color:r,children:e}),o(h,{color:i.muted,children:t})]})}export{or as PracticeScreen};
2
+ //# sourceMappingURL=PracticeScreen-LM6M2OYD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/screens/PracticeScreen.tsx","../src/util/shuffle.ts","../src/domain/chapters.ts","../src/domain/input-buffer.ts","../src/domain/session.ts","../src/ui/hooks/useWordLoop.ts","../src/ui/hooks/useAudio.ts","../src/ui/hooks/useSessionPersistence.ts","../src/ui/screens/StealthPracticeLayout.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { Word } from '../../domain/dictionary.js';\nimport type { Mode } from '../../domain/chapters.js';\nimport { chunkChapters, buildPlaylist } from '../../domain/chapters.js';\nimport { sessionSummary } from '../../domain/session.js';\nimport { loadMistakes } from '../../domain/mistakes.js';\nimport { ensureDictionary } from '../../infra/dict-downloader.js';\nimport { useWordLoop } from '../hooks/useWordLoop.js';\nimport { useAudio } from '../hooks/useAudio.js';\nimport { useSessionPersistence } from '../hooks/useSessionPersistence.js';\nimport { useNav, type PracticeParams } from '../nav.js';\nimport { useAppState } from '../app-state.js';\nimport { useAudioStatus } from '../audio-context.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { BigWord, PALETTE } from '../components/BigWord.js';\nimport { truncateName } from '../../util/dict-name.js';\nimport { StealthTyping, StealthPaused, StealthSummary } from './StealthPracticeLayout.js';\n\ntype Phase = 'loading' | 'typing' | 'paused' | 'summary' | 'error';\n\ntype Loaded = {\n playlist: Word[];\n totalChapters: number;\n};\n\nexport function PracticeScreen({ params }: { params: PracticeParams }) {\n const { dictId, chapterIndex, mode } = params;\n const { cfg } = useAppState();\n const t = useStrings();\n\n const [phase, setPhase] = useState<Phase>('loading');\n const [loaded, setLoaded] = useState<Loaded | null>(null);\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n setPhase('loading');\n setLoaded(null);\n setErrorMsg(null);\n (async () => {\n try {\n const words = await ensureDictionary(dictId);\n if (cancelled) return;\n if (mode === 'review') {\n const book = await loadMistakes();\n if (cancelled) return;\n const reviewWords = words.filter((w) => book[w.name]?.count).slice(0, cfg.chapterSize);\n if (reviewWords.length === 0) {\n setErrorMsg(t.practice.errors.noMistakes);\n setPhase('error');\n return;\n }\n setLoaded({ playlist: reviewWords, totalChapters: 1 });\n setPhase('typing');\n return;\n }\n const chapters = chunkChapters(words, cfg.chapterSize);\n if (chapters.length === 0) {\n setErrorMsg(t.practice.errors.dictEmpty(dictId));\n setPhase('error');\n return;\n }\n const idx = Math.max(0, Math.min(chapters.length - 1, chapterIndex));\n const playlist = buildPlaylist(chapters[idx]!, mode);\n setLoaded({ playlist, totalChapters: chapters.length });\n setPhase('typing');\n } catch (err) {\n if (cancelled) return;\n setErrorMsg((err as Error).message);\n setPhase('error');\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [dictId, chapterIndex, mode, cfg.chapterSize, t]);\n\n if (phase === 'loading') {\n return <Centered text={t.practice.loading} color={PALETTE.muted} />;\n }\n if (phase === 'error') {\n return <ErrorView msg={errorMsg ?? t.practice.errors.unknown} />;\n }\n if (!loaded) return null;\n\n return (\n <PracticeRunner\n key={`${dictId}-${chapterIndex}-${mode}-${params.stealth ? 's' : 'n'}`}\n params={params}\n loaded={loaded}\n phase={phase}\n setPhase={setPhase}\n />\n );\n}\n\nfunction PracticeRunner({\n params,\n loaded,\n phase,\n setPhase,\n}: {\n params: PracticeParams;\n loaded: Loaded;\n phase: Phase;\n setPhase: (p: Phase) => void;\n}) {\n const { dictId, chapterIndex, mode } = params;\n const stealth = params.stealth === true;\n const { cfg } = useAppState();\n const nav = useNav();\n const { exit } = useApp();\n const goBack = () => (nav.stack.length > 1 ? nav.back() : exit());\n const persist = useSessionPersistence({ dictId, chapterIndex, mode });\n const dictName = useDictName(dictId);\n\n const audio = useAudio({\n enabled: !stealth && cfg.sounds.master,\n accent: cfg.accent,\n autoplayPronunciation: !stealth && cfg.autoplayPronunciation,\n });\n const audioStatus = useAudioStatus();\n // Only surface the warning when the user opted in to sounds — if they\n // disabled sounds via config, \"no player found\" isn't a problem to flag.\n const audioWarn = cfg.sounds.master ? audioStatus.warning : null;\n\n const finishedRef = useRef(false);\n const lastEffectRef = useRef<string | null>(null);\n const lastIndexRef = useRef<number>(-1);\n const [infoVisible, setInfoVisible] = useState(false);\n const [infoShownAt, setInfoShownAt] = useState<number | null>(null);\n const [imeBlocked, setImeBlocked] = useState(false);\n\n useEffect(() => {\n if (infoShownAt === null) return;\n const id = setTimeout(() => setInfoVisible(false), 2000);\n return () => clearTimeout(id);\n }, [infoShownAt]);\n\n const { session, lastEffect, tick } = useWordLoop({\n playlist: loaded.playlist,\n enabled: phase === 'typing',\n onComplete: (s) => {\n if (finishedRef.current) return;\n finishedRef.current = true;\n setPhase('summary');\n Promise.resolve(persist(sessionSummary(s))).catch((err) => {\n console.error('Failed to persist session:', err);\n });\n },\n onEscape: () => setPhase(phase === 'paused' ? 'typing' : 'paused'),\n onTab: stealth\n ? undefined\n : () => {\n const cur = session.current ? loaded.playlist[session.current.wordIndex] : undefined;\n if (cur) void audio.pronounce(cur.name);\n },\n onImeBlock: () => setImeBlocked(true),\n onValidInput: () => setImeBlocked(false),\n });\n\n useEffect(() => {\n if (stealth) return;\n if (lastEffect === null) return;\n if (lastEffect === lastEffectRef.current) return;\n lastEffectRef.current = lastEffect;\n if (lastEffect === 'wrong' && cfg.sounds.feedback) audio.wrong();\n if (lastEffect === 'progress' && cfg.sounds.keystroke) audio.keystroke();\n if (lastEffect === 'correct') {\n if (cfg.sounds.feedback) audio.correct();\n if (cfg.sounds.keystroke) audio.keystroke();\n }\n }, [stealth, lastEffect, audio, cfg.sounds.feedback, cfg.sounds.keystroke]);\n\n useEffect(() => {\n if (stealth) return;\n const idx = session.current?.wordIndex ?? -1;\n if (idx === -1) return;\n if (idx === lastIndexRef.current) return;\n lastIndexRef.current = idx;\n const cur = loaded.playlist[idx];\n const next = loaded.playlist[idx + 1];\n if (cur && cfg.autoplayPronunciation) audio.pronounce(cur.name);\n if (next) audio.prefetch(next.name);\n }, [stealth, session.current?.wordIndex, audio, cfg.autoplayPronunciation, loaded.playlist]);\n\n void tick;\n\n useInput(\n (_input, key) => {\n // Node's readline normalizes byte 0x09 (Ctrl+I) to {name:'tab', ctrl:false},\n // so key.ctrl && input === 'i' would never match. Tab and Ctrl+I both arrive\n // here as key.tab — bind to that. In stealth mode Tab has no other use\n // (onTab is disabled below), so this is non-conflicting.\n if (key.tab) {\n setInfoVisible(true);\n setInfoShownAt(Date.now());\n return;\n }\n },\n { isActive: stealth && phase === 'typing' },\n );\n\n useInput(\n (_input, key) => {\n if (key.return) {\n setPhase('typing');\n return;\n }\n if (key.escape) {\n goBack();\n return;\n }\n },\n { isActive: phase === 'paused' },\n );\n\n useInput(\n (input, key) => {\n if (key.escape) {\n goBack();\n return;\n }\n if (key.return) {\n const nextIdx = chapterIndex + 1;\n if (mode === 'loop') {\n nav.replace({\n name: 'practice',\n params: { dictId, chapterIndex, mode, stealth: params.stealth },\n });\n } else if (mode === 'review' || nextIdx >= loaded.totalChapters) {\n goBack();\n } else {\n nav.replace({\n name: 'practice',\n params: { dictId, chapterIndex: nextIdx, mode, stealth: params.stealth },\n });\n }\n return;\n }\n if (input === 'm') {\n nav.replace({\n name: 'practice',\n params: { dictId, chapterIndex: 0, mode: 'review', stealth: params.stealth },\n });\n return;\n }\n },\n { isActive: phase === 'summary' },\n );\n\n const completed = session.results.length;\n const errors = session.results.reduce((a, r) => a + r.errors, 0);\n const elapsedMs = Date.now() - session.startedAt;\n const minutes = elapsedMs / 60000;\n const wpm = minutes > 0 ? Math.round((completed / minutes) * 10) / 10 : 0;\n\n const summary = phase === 'summary' ? sessionSummary(session) : null;\n\n if (stealth) {\n if (phase === 'paused') return <StealthPaused />;\n if (phase === 'summary' && summary) {\n const sMinutes = summary.durationMs / 60000;\n const sWpm = sMinutes > 0 ? Math.round((summary.wordCount / sMinutes) * 10) / 10 : 0;\n const sErrWords = Object.keys(summary.perWordErrors).length;\n const sAcc =\n summary.wordCount === 0 ? 1 : Math.max(0, (summary.wordCount - sErrWords) / summary.wordCount);\n const sAccPct = Math.round(sAcc * 1000) / 10;\n return (\n <StealthSummary\n wordCount={summary.wordCount}\n errors={summary.errors}\n durationMs={summary.durationMs}\n wpm={sWpm}\n accPct={sAccPct}\n />\n );\n }\n const currentWord = session.current\n ? loaded.playlist[session.current.wordIndex]\n : loaded.playlist[loaded.playlist.length - 1];\n const inputState = session.current?.input ?? { target: '', typed: '', errorsThisWord: 0 };\n const errWords = new Set(\n session.results.filter((r) => r.errors > 0).map((r) => r.word),\n ).size;\n const accFrac =\n completed === 0 ? 1 : Math.max(0, (completed - errWords) / completed);\n const accPct = Math.round(accFrac * 1000) / 10;\n const chapterLabel =\n mode === 'review'\n ? 'review'\n : `ch ${chapterIndex + 1}/${loaded.totalChapters}`;\n return (\n <StealthTyping\n target={currentWord?.name ?? ''}\n typed={inputState.typed}\n hideTarget={mode === 'dictation'}\n phonetic={pickPhonetic(currentWord, cfg.accent)}\n translation={currentWord?.trans ?? []}\n error={lastEffect === 'wrong'}\n imeBlocked={imeBlocked}\n audioWarning={audioWarn}\n info={{\n visible: infoVisible,\n dictName: truncateName(dictName, 24),\n chapterLabel,\n completed,\n total: loaded.playlist.length,\n wpm,\n accPct,\n elapsedMs,\n }}\n />\n );\n }\n\n if (phase === 'paused') {\n return (\n <PausedView\n dictName={dictName}\n chapterIndex={chapterIndex}\n totalChapters={loaded.totalChapters}\n mode={mode}\n completed={completed}\n total={loaded.playlist.length}\n />\n );\n }\n\n if (phase === 'summary' && summary) {\n return (\n <SummaryView\n dictName={dictName}\n chapterIndex={chapterIndex}\n totalChapters={loaded.totalChapters}\n mode={mode}\n summary={summary}\n />\n );\n }\n\n const currentWord = session.current\n ? loaded.playlist[session.current.wordIndex]\n : loaded.playlist[loaded.playlist.length - 1];\n const inputState = session.current?.input ?? { target: '', typed: '', errorsThisWord: 0 };\n\n return (\n <TypingLayout\n dictName={dictName}\n chapterIndex={chapterIndex}\n totalChapters={loaded.totalChapters}\n mode={mode}\n accent={cfg.accent}\n completed={completed}\n total={loaded.playlist.length}\n errors={errors}\n wpm={wpm}\n elapsedMs={elapsedMs}\n target={currentWord?.name ?? ''}\n typed={inputState.typed}\n flashError={lastEffect === 'wrong'}\n hideTarget={mode === 'dictation'}\n phonetic={pickPhonetic(currentWord, cfg.accent)}\n translation={currentWord?.trans ?? []}\n imeBlocked={imeBlocked}\n audioWarning={audioWarn}\n />\n );\n}\n\nfunction pickPhonetic(word: Word | undefined, accent: 'us' | 'uk'): string | null {\n if (!word) return null;\n const p = accent === 'us' ? word.usphone : word.ukphone;\n return p ?? null;\n}\n\nfunction fmtTime(ms: number): string {\n const total = Math.floor(ms / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\nfunction TypingLayout(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n accent: 'us' | 'uk';\n completed: number;\n total: number;\n errors: number;\n wpm: number;\n elapsedMs: number;\n target: string;\n typed: string;\n flashError: boolean;\n hideTarget: boolean;\n phonetic: string | null;\n translation: string[];\n imeBlocked: boolean;\n audioWarning: string | null;\n}) {\n const t = useStrings();\n const progressFrac = props.total === 0 ? 0 : props.completed / props.total;\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <StatusBar\n dictName={props.dictName}\n chapterIndex={props.chapterIndex}\n totalChapters={props.totalChapters}\n mode={props.mode}\n accent={props.accent}\n completed={props.completed}\n total={props.total}\n elapsedMs={props.elapsedMs}\n />\n\n <Box flexGrow={1} flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\">\n <BigWord\n target={props.target}\n typed={props.typed}\n error={props.flashError}\n hideTarget={props.hideTarget}\n />\n\n {props.phonetic && (\n <Box marginTop={1}>\n <Text italic dimColor color={PALETTE.muted}>\n {props.phonetic}\n </Text>\n </Box>\n )}\n\n {props.translation.length > 0 && (\n <Box marginTop={1} flexDirection=\"column\" alignItems=\"center\">\n {props.translation.slice(0, 2).map((tr, i) => (\n <Text key={i} color={PALETTE.primary}>\n {tr}\n </Text>\n ))}\n </Box>\n )}\n\n {props.imeBlocked && (\n <Box marginTop={1}>\n <Text color={PALETTE.warning}>{t.practice.imeWarning}</Text>\n </Box>\n )}\n\n {!props.imeBlocked && props.audioWarning && (\n <Box marginTop={1}>\n <Text color={PALETTE.warning}>{props.audioWarning}</Text>\n </Box>\n )}\n </Box>\n\n <Box flexDirection=\"column\">\n <ProgressBar frac={progressFrac} />\n <Box justifyContent=\"center\" marginTop={1}>\n <Text color={PALETTE.muted}>\n {props.completed}/{props.total} · {fmtTime(props.elapsedMs)} · {props.wpm} {t.practice.statCards.wpm} · {props.errors} {t.practice.statCards.errors}\n </Text>\n </Box>\n <Box justifyContent=\"center\" marginTop={1}>\n <Text color={PALETTE.muted}>{t.practice.footers.typing}</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n\nfunction StatusBar(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n accent: 'us' | 'uk';\n completed: number;\n total: number;\n elapsedMs: number;\n}) {\n const t = useStrings();\n const modeName = t.practice.modes[props.mode];\n const accentName = t.practice.accents[props.accent];\n const name = truncateName(props.dictName, 20);\n const left =\n props.mode === 'review'\n ? `${name} · ${t.practice.reviewLabel} · ${accentName}`\n : `${name} · ${t.practice.chapterLabel(props.chapterIndex + 1, props.totalChapters)} · ${modeName} · ${accentName}`;\n const right = `${props.completed}/${props.total} · ${fmtTime(props.elapsedMs)}`;\n return (\n <Box>\n <Text color={PALETTE.muted}>{left}</Text>\n <Box flexGrow={1} />\n <Text color={PALETTE.muted}>{right}</Text>\n </Box>\n );\n}\n\nfunction ProgressBar({ frac }: { frac: number }) {\n const cols = process.stdout.columns ?? 80;\n const width = Math.max(20, Math.min(72, cols - 16));\n const filled = Math.round(width * Math.max(0, Math.min(1, frac)));\n const empty = width - filled;\n return (\n <Box justifyContent=\"center\">\n <Text color={PALETTE.accent}>{'━'.repeat(filled)}</Text>\n <Text color={PALETTE.muted}>{'─'.repeat(empty)}</Text>\n </Box>\n );\n}\n\nfunction PausedView(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n completed: number;\n total: number;\n}) {\n const t = useStrings();\n const frac = props.total === 0 ? 0 : props.completed / props.total;\n const subtitle =\n props.mode === 'review'\n ? `${truncateName(props.dictName, 20)} · ${t.practice.reviewLabel}`\n : `${truncateName(props.dictName, 20)} · ${t.practice.pause.chapter(props.chapterIndex + 1, props.totalChapters)}`;\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.warning}>\n {t.practice.pause.title}\n </Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{subtitle}</Text>\n </Box>\n <Box marginTop={2}>\n <ProgressBar frac={frac} />\n </Box>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.practice.pause.progress(props.completed, props.total)}</Text>\n </Box>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>{t.practice.pause.hint}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction ErrorView({ msg }: { msg: string }) {\n const t = useStrings();\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.error}>{msg}</Text>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>Esc {t.common.back}</Text>\n </Box>\n <BackKey />\n </Box>\n );\n}\n\nfunction BackKey() {\n const nav = useNav();\n useInput((_input, key) => {\n if (key.escape) nav.back();\n });\n return null;\n}\n\nfunction Centered({ text, color }: { text: string; color: string }) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={color}>{text}</Text>\n </Box>\n );\n}\n\nfunction SummaryView(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n summary: { wordCount: number; errors: number; durationMs: number; perWordErrors: Record<string, number> };\n}) {\n const { summary } = props;\n const minutes = summary.durationMs / 60000;\n const wpm = minutes > 0 ? Math.round((summary.wordCount / minutes) * 10) / 10 : 0;\n const errorWords = Object.keys(summary.perWordErrors).length;\n const acc = summary.wordCount === 0 ? 1 : Math.max(0, (summary.wordCount - errorWords) / summary.wordCount);\n const accPct = Math.round(acc * 1000) / 10;\n\n const t = useStrings();\n const modeName = t.practice.modes[props.mode];\n const name = truncateName(props.dictName, 20);\n const subtitle =\n props.mode === 'review'\n ? `${name} · ${t.practice.reviewLabel}`\n : `${name} · ${t.practice.chapterLabel(props.chapterIndex + 1, props.totalChapters)} · ${modeName}`;\n\n const nextLabel =\n props.mode === 'loop'\n ? t.practice.summary.loopAgain\n : props.mode === 'review' || props.chapterIndex + 1 >= props.totalChapters\n ? t.practice.summary.backMenu\n : t.practice.summary.nextChapter;\n const footer = `Enter ${nextLabel} · m ${t.practice.summary.reviewMistakes} · Esc ${t.practice.summary.backMenu}`;\n\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" paddingY={1} width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.success}>\n {t.practice.chapterComplete}\n </Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{subtitle}</Text>\n </Box>\n\n <Box marginTop={3} flexDirection=\"row\" justifyContent=\"center\">\n <StatCard label={t.practice.statCards.words} value={String(summary.wordCount)} color={PALETTE.text} />\n <StatCard\n label={t.practice.statCards.errors}\n value={String(summary.errors)}\n color={summary.errors > 0 ? PALETTE.error : PALETTE.muted}\n />\n <StatCard label={t.practice.statCards.wpm} value={String(wpm)} color={PALETTE.accent} />\n <StatCard label={t.practice.statCards.accuracy} value={`${accPct}%`} color={PALETTE.accent} />\n </Box>\n\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>{t.practice.statCards.elapsed(fmtTime(summary.durationMs))}</Text>\n </Box>\n\n <Box flexGrow={1} />\n\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>{footer}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction StatCard({ label, value, color }: { label: string; value: string; color: string }) {\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" marginX={3}>\n <Text bold color={color}>{value}</Text>\n <Text color={PALETTE.muted}>{label}</Text>\n </Box>\n );\n}\n","export function shuffle<T>(arr: readonly T[], rng: () => number = Math.random): T[] {\n const out = [...arr];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1));\n const tmp = out[i]!;\n out[i] = out[j]!;\n out[j] = tmp;\n }\n return out;\n}\n\nexport function mulberry32(seed: number): () => number {\n let t = seed >>> 0;\n return () => {\n t = (t + 0x6d2b79f5) >>> 0;\n let r = Math.imul(t ^ (t >>> 15), 1 | t);\n r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;\n return ((r ^ (r >>> 14)) >>> 0) / 4294967296;\n };\n}\n","import type { Word } from './dictionary.js';\nimport { shuffle, mulberry32 } from '../util/shuffle.js';\n\nexport type Mode = 'order' | 'dictation' | 'review' | 'random' | 'loop';\n\nexport function chunkChapters(words: Word[], chapterSize: number): Word[][] {\n if (chapterSize <= 0) throw new Error('chapterSize must be positive');\n const chunks: Word[][] = [];\n for (let i = 0; i < words.length; i += chapterSize) {\n chunks.push(words.slice(i, i + chapterSize));\n }\n return chunks;\n}\n\nexport function chapterCount(totalWords: number, chapterSize: number): number {\n return Math.ceil(totalWords / chapterSize);\n}\n\n/**\n * Build a play list for a chapter, applying the given mode.\n * - order: return as-is\n * - dictation: same as order (hide-the-word behavior is UI-only)\n * - random: single shuffled pass\n * - loop: return as-is; the practice screen drives the repeat\n * - review: caller passes the mistake-book words, we just chunk those\n */\nexport function buildPlaylist(chapter: Word[], mode: Mode, seed?: number): Word[] {\n if (mode === 'random') {\n const rng = seed === undefined ? Math.random : mulberry32(seed);\n return shuffle(chapter, rng);\n }\n return chapter;\n}\n","export type InputState = {\n target: string;\n typed: string;\n errorsThisWord: number;\n};\n\nexport type InputEvent =\n | { type: 'char'; ch: string }\n | { type: 'backspace' }\n | { type: 'reset' };\n\nexport type InputEffect = 'none' | 'progress' | 'wrong' | 'correct' | 'skipped';\n\nexport function initialState(target: string): InputState {\n return { target, typed: '', errorsThisWord: 0 };\n}\n\nexport function reduce(state: InputState, ev: InputEvent): { state: InputState; effect: InputEffect } {\n switch (ev.type) {\n case 'reset':\n return { state: { ...state, typed: '' }, effect: 'none' };\n case 'backspace': {\n if (state.typed.length === 0) return { state, effect: 'none' };\n return { state: { ...state, typed: state.typed.slice(0, -1) }, effect: 'none' };\n }\n case 'char': {\n const candidate = state.typed + ev.ch;\n // Compare by code-point index, not byte length, to handle unicode safely.\n const targetUpToCandidate = [...state.target].slice(0, [...candidate].length).join('');\n if (candidate === targetUpToCandidate) {\n if (candidate.length === state.target.length) {\n return { state: { ...state, typed: candidate }, effect: 'correct' };\n }\n return { state: { ...state, typed: candidate }, effect: 'progress' };\n }\n return {\n state: { ...state, typed: '', errorsThisWord: state.errorsThisWord + 1 },\n effect: 'wrong',\n };\n }\n }\n}\n","import type { Word } from './dictionary.js';\nimport { initialState, reduce, type InputEvent, type InputState, type InputEffect } from './input-buffer.js';\n\nexport type SessionWordResult = { word: string; errors: number; durationMs: number; skipped?: boolean };\n\nexport type Session = {\n startedAt: number;\n results: SessionWordResult[];\n current: { wordIndex: number; wordStartedAt: number; input: InputState } | null;\n finishedAt: number | null;\n playlist: Word[];\n};\n\nexport function startSession(playlist: Word[], now = Date.now()): Session {\n if (playlist.length === 0) {\n return { startedAt: now, results: [], current: null, finishedAt: now, playlist };\n }\n return {\n startedAt: now,\n results: [],\n current: { wordIndex: 0, wordStartedAt: now, input: initialState(playlist[0]!.name) },\n finishedAt: null,\n playlist,\n };\n}\n\nexport function feedSession(session: Session, ev: InputEvent, now = Date.now()): { session: Session; effect: InputEffect } {\n if (!session.current) return { session, effect: 'none' };\n const { state, effect } = reduce(session.current.input, ev);\n if (effect === 'correct') {\n const finished: SessionWordResult = {\n word: state.target,\n errors: state.errorsThisWord,\n durationMs: now - session.current.wordStartedAt,\n };\n const nextIndex = session.current.wordIndex + 1;\n const results = [...session.results, finished];\n if (nextIndex >= session.playlist.length) {\n return {\n session: { ...session, results, current: null, finishedAt: now },\n effect,\n };\n }\n return {\n session: {\n ...session,\n results,\n current: {\n wordIndex: nextIndex,\n wordStartedAt: now,\n input: initialState(session.playlist[nextIndex]!.name),\n },\n },\n effect,\n };\n }\n return {\n session: {\n ...session,\n current: { ...session.current, input: state },\n },\n effect,\n };\n}\n\nexport function skipSession(session: Session, now = Date.now()): { session: Session; effect: InputEffect } {\n if (!session.current) return { session, effect: 'none' };\n const result: SessionWordResult = {\n word: session.current.input.target,\n errors: 0,\n durationMs: now - session.current.wordStartedAt,\n skipped: true,\n };\n const nextIndex = session.current.wordIndex + 1;\n const results = [...session.results, result];\n if (nextIndex >= session.playlist.length) {\n return {\n session: { ...session, results, current: null, finishedAt: now },\n effect: 'skipped',\n };\n }\n return {\n session: {\n ...session,\n results,\n current: {\n wordIndex: nextIndex,\n wordStartedAt: now,\n input: initialState(session.playlist[nextIndex]!.name),\n },\n },\n effect: 'skipped',\n };\n}\n\nexport function sessionSummary(session: Session): {\n wordCount: number;\n errors: number;\n durationMs: number;\n perWordErrors: Record<string, number>;\n} {\n const errors = session.results.reduce((a, r) => a + r.errors, 0);\n const durationMs =\n (session.finishedAt ?? Date.now()) - session.startedAt;\n const perWordErrors: Record<string, number> = {};\n for (const r of session.results) {\n if (r.errors > 0) perWordErrors[r.word] = (perWordErrors[r.word] ?? 0) + r.errors;\n }\n return { wordCount: session.results.length, errors, durationMs, perWordErrors };\n}\n","import { useEffect, useReducer, useRef, useState } from 'react';\nimport { useInput, useApp } from 'ink';\nimport type { Word } from '../../domain/dictionary.js';\nimport { startSession, feedSession, skipSession, type Session } from '../../domain/session.js';\nimport type { InputEffect } from '../../domain/input-buffer.js';\n\ntype Action =\n | { type: 'event'; input: string; key: { backspace?: boolean; delete?: boolean; tab?: boolean; escape?: boolean; return?: boolean; ctrl?: boolean }; now: number }\n | { type: 'start'; playlist: Word[]; now: number }\n | { type: 'skip'; now: number };\n\nfunction reducer(state: { session: Session; lastEffect: InputEffect | null }, action: Action) {\n if (action.type === 'start') {\n return { session: startSession(action.playlist, action.now), lastEffect: null };\n }\n if (action.type === 'skip') {\n const r = skipSession(state.session, action.now);\n return { session: r.session, lastEffect: r.effect };\n }\n if (action.type === 'event') {\n if (action.key.backspace || action.key.delete) {\n const r = feedSession(state.session, { type: 'backspace' }, action.now);\n return { session: r.session, lastEffect: r.effect };\n }\n if (action.input.length === 0) return state;\n let session = state.session;\n let lastEffect: InputEffect | null = state.lastEffect;\n for (const c of action.input) {\n const r = feedSession(session, { type: 'char', ch: c }, action.now);\n session = r.session;\n lastEffect = r.effect;\n if (session.finishedAt !== null) break;\n }\n return { session, lastEffect };\n }\n return state;\n}\n\n// Classify a typed batch from Ink's useInput. Single source of truth so the\n// IME-detection logic stays unit-testable without rendering Ink.\n// kind='ime' → contains at least one non-ASCII codepoint (>= 0x80, e.g.\n// CJK / Latin-1 supplement); reject the batch and show hint.\n// kind='valid' → all-ASCII printable; `cleaned` has the dispatchable subset.\n// kind='noise' → ASCII control (DEL, NUL, etc.) or empty; ignore silently.\nexport type InputBatchKind = 'ime' | 'valid' | 'noise';\nexport function classifyInputBatch(input: string): { kind: InputBatchKind; cleaned: string } {\n const chars = [...input];\n if (chars.some((c) => c.codePointAt(0)! >= 0x80)) {\n return { kind: 'ime', cleaned: '' };\n }\n const cleaned = chars\n .filter((c) => {\n const cp = c.codePointAt(0)!;\n return cp >= 0x20 && cp <= 0x7e;\n })\n .join('');\n return { kind: cleaned.length > 0 ? 'valid' : 'noise', cleaned };\n}\n\nexport type UseWordLoopOpts = {\n playlist: Word[];\n onComplete: (session: Session) => void;\n onTab?: () => void;\n onEscape?: () => void;\n onSkip?: () => void;\n // Fires when the typed batch contains any codepoint > 0x7E — almost always\n // means the user's IME is active (CJK input method). The batch is rejected\n // entirely so the wrong characters aren't fed to the session.\n onImeBlock?: () => void;\n // Fires when a non-empty all-ASCII batch is about to dispatch. Used to\n // clear a previously-set IME warning so it auto-dismisses on the first\n // successful keystroke.\n onValidInput?: () => void;\n enabled?: boolean;\n};\n\nexport function useWordLoop({\n playlist,\n onComplete,\n onTab,\n onEscape,\n onSkip,\n onImeBlock,\n onValidInput,\n enabled = true,\n}: UseWordLoopOpts) {\n const [state, dispatch] = useReducer(reducer, undefined, () => ({\n session: startSession(playlist, Date.now()),\n lastEffect: null as InputEffect | null,\n }));\n const completedRef = useRef(false);\n const [tick, setTick] = useState(0);\n const { exit } = useApp();\n\n useInput(\n (input, key) => {\n if (key.ctrl && input === 'c') {\n exit();\n return;\n }\n if (key.ctrl && input === 'n') {\n onSkip?.();\n dispatch({ type: 'skip', now: Date.now() });\n return;\n }\n if (key.escape) {\n onEscape?.();\n return;\n }\n if (key.tab) {\n onTab?.();\n return;\n }\n if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.return) return;\n if (key.ctrl || key.meta) return;\n const { kind, cleaned } = classifyInputBatch(input);\n if (kind === 'ime') {\n onImeBlock?.();\n return;\n }\n if (kind === 'noise') return;\n onValidInput?.();\n dispatch({ type: 'event', input: cleaned, key, now: Date.now() });\n },\n { isActive: enabled },\n );\n\n useEffect(() => {\n if (state.session.finishedAt !== null && !completedRef.current) {\n completedRef.current = true;\n onComplete(state.session);\n }\n }, [state.session, onComplete]);\n\n useEffect(() => {\n if (state.session.finishedAt !== null) return;\n const id = setInterval(() => setTick((t) => t + 1), 1000);\n return () => clearInterval(id);\n }, [state.session.finishedAt]);\n\n return { session: state.session, lastEffect: state.lastEffect, tick };\n}\n","import { useEffect, useRef } from 'react';\nimport {\n initAudio,\n playCorrect,\n playWrong,\n playKeystroke,\n playPronunciation,\n prefetchPronunciation,\n} from '../../infra/audio.js';\n\ntype Opts = {\n enabled: boolean;\n accent: 'us' | 'uk';\n autoplayPronunciation: boolean;\n};\n\nexport type AudioControls = {\n keystroke: () => void;\n correct: () => void;\n wrong: () => void;\n pronounce: (word: string) => void;\n prefetch: (word: string) => void;\n};\n\nexport function useAudio(opts: Opts): AudioControls {\n const initedRef = useRef(false);\n useEffect(() => {\n if (initedRef.current) return;\n initedRef.current = true;\n initAudio(!opts.enabled).catch(() => undefined);\n }, [opts.enabled]);\n\n return {\n keystroke: () => opts.enabled && playKeystroke(),\n correct: () => opts.enabled && playCorrect(),\n wrong: () => opts.enabled && playWrong(),\n pronounce: (word) => {\n if (!opts.enabled) return;\n if (opts.autoplayPronunciation) void playPronunciation(word, opts.accent);\n },\n prefetch: (word) => {\n if (!opts.enabled) return;\n void prefetchPronunciation(word, opts.accent);\n },\n };\n}\n","import { useCallback } from 'react';\nimport { appendSession, type SessionRecord } from '../../domain/stats.js';\nimport { loadMistakes, saveMistakes, bump } from '../../domain/mistakes.js';\nimport { addChapter as trackChapter } from '../../infra/session-tracker.js';\nimport type { Mode } from '../../domain/chapters.js';\n\ntype Summary = {\n wordCount: number;\n errors: number;\n durationMs: number;\n perWordErrors: Record<string, number>;\n};\n\nexport function useSessionPersistence(meta: { dictId: string; chapterIndex: number; mode: Mode }) {\n return useCallback(\n async (summary: Summary): Promise<void> => {\n const rec: SessionRecord = {\n ts: new Date().toISOString(),\n dictId: meta.dictId,\n chapter: meta.chapterIndex,\n mode: meta.mode,\n wordCount: summary.wordCount,\n errors: summary.errors,\n durationMs: summary.durationMs,\n perWordErrors: summary.perWordErrors,\n };\n await appendSession(rec);\n trackChapter({\n dictId: meta.dictId,\n chapterIndex: meta.chapterIndex,\n mode: meta.mode,\n wordCount: summary.wordCount,\n errors: summary.errors,\n durationMs: summary.durationMs,\n perWordErrors: summary.perWordErrors,\n });\n const dirty = Object.entries(summary.perWordErrors).filter(([, n]) => n > 0);\n if (dirty.length === 0) return;\n let book = await loadMistakes();\n for (const [word, n] of dirty) book = bump(book, word, meta.dictId, n);\n await saveMistakes(book);\n },\n [meta.dictId, meta.chapterIndex, meta.mode],\n );\n}\n","import type { ReactNode } from 'react';\nimport { Box, Text, useStdout } from 'ink';\nimport { PALETTE } from '../components/BigWord.js';\nimport { useStrings } from '../../i18n/context.js';\n\nconst RIGHT_WIDTH = 28;\n\nfunction fmtTime(ms: number): string {\n const total = Math.floor(ms / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\nfunction useLeftWidth(): number {\n const { stdout } = useStdout();\n const cols = stdout?.columns ?? 80;\n return Math.max(20, cols - RIGHT_WIDTH);\n}\n\nfunction Row({ left, right }: { left: ReactNode; right: ReactNode }) {\n const leftWidth = useLeftWidth();\n return (\n <Box>\n <Box width={leftWidth}>{left}</Box>\n <Box width={RIGHT_WIDTH} justifyContent=\"flex-end\">\n {right}\n </Box>\n </Box>\n );\n}\n\nexport function StealthTyping(props: {\n target: string;\n typed: string;\n hideTarget: boolean;\n phonetic: string | null;\n translation: string[];\n error: boolean;\n imeBlocked: boolean;\n audioWarning: string | null;\n info: {\n visible: boolean;\n dictName: string;\n chapterLabel: string;\n completed: number;\n total: number;\n wpm: number;\n accPct: number;\n elapsedMs: number;\n };\n}) {\n const t = useStrings();\n const target = [...props.target];\n const typed = [...props.typed];\n\n // Row 1: word chars + inline phonetic (two-space gap, italic + dim muted).\n // error=true flashes the entire word red; phonetic remains dim regardless.\n const wordCell = (\n <Box>\n {target.map((ch, i) => {\n const isTyped = i < typed.length;\n const display = props.hideTarget && !isTyped ? '_' : isTyped ? typed[i]! : ch;\n const color = props.error ? PALETTE.error : isTyped ? PALETTE.accent : PALETTE.muted;\n return (\n <Text key={i} bold color={color}>\n {display}\n </Text>\n );\n })}\n {props.phonetic && (\n <>\n <Text> </Text>\n <Text italic dimColor color={PALETTE.muted}>\n {props.phonetic}\n </Text>\n </>\n )}\n </Box>\n );\n\n // Row 3: first translation in primary cyan (matches TypingLayout translation color)\n const translationCell = props.translation.length > 0 ? (\n <Text color={PALETTE.primary}>{props.translation[0]!}</Text>\n ) : (\n <Text> </Text>\n );\n\n const info = props.info;\n const accFmt = Number.isInteger(info.accPct) ? `${info.accPct}` : info.accPct.toFixed(1);\n\n // Right column priority: imeBlocked > audioWarning > info > idle.\n // imeBlocked: warning indicator on row 1, rows 2/3 blank.\n // audioWarning (and no IME issue): `! audio` short marker on row 1, rows 2/3 blank.\n // info.visible: dict / progress / time on rows 1/2/3.\n // idle: all three rows blank.\n const showAudio = !props.imeBlocked && props.audioWarning !== null;\n const right1 = props.imeBlocked ? (\n <Text color={PALETTE.warning}>{t.practice.imeWarningShort}</Text>\n ) : showAudio ? (\n <Text color={PALETTE.warning}>{t.practice.audioWarningShort}</Text>\n ) : info.visible ? (\n <Text color={PALETTE.muted}>{`${info.dictName} · ${info.chapterLabel}`}</Text>\n ) : (\n <Text> </Text>\n );\n const right2 = props.imeBlocked || showAudio ? (\n <Text> </Text>\n ) : info.visible ? (\n <Text color={PALETTE.muted}>{`${info.completed}/${info.total} · ${info.wpm}wpm · ${accFmt}%`}</Text>\n ) : (\n <Text> </Text>\n );\n const right3 = props.imeBlocked || showAudio ? (\n <Text> </Text>\n ) : info.visible ? (\n <Text color={PALETTE.muted}>{fmtTime(info.elapsedMs)}</Text>\n ) : (\n <Text> </Text>\n );\n\n // Layout: 3 rendered rows. Middle row's left is blank → acts as a 1-line\n // visual gap between word+phonetic and translation. Right column keeps all\n // three info slots (dict / progress / time) when info.visible is true.\n return (\n <Box flexDirection=\"column\">\n <Row left={wordCell} right={right1} />\n <Row left={<Text> </Text>} right={right2} />\n <Row left={translationCell} right={right3} />\n </Box>\n );\n}\n\nexport function StealthPaused() {\n const t = useStrings();\n return (\n <Box flexDirection=\"column\">\n <Row\n left={<Text color={PALETTE.warning}>{t.stealth.paused}</Text>}\n right={<Text color={PALETTE.muted}>{t.stealth.pausedHintRight}</Text>}\n />\n <Row left={<Text> </Text>} right={<Text color={PALETTE.muted}>Esc {t.common.back}</Text>} />\n <Row left={<Text> </Text>} right={<Text> </Text>} />\n </Box>\n );\n}\n\nexport function StealthSummary(props: {\n wordCount: number;\n errors: number;\n durationMs: number;\n wpm: number;\n accPct: number;\n}) {\n const t = useStrings();\n const accFmt = Number.isInteger(props.accPct) ? `${props.accPct}` : props.accPct.toFixed(1);\n const line = `${t.stealth.chapterDone} · ${props.wordCount}w · ${props.wpm}wpm · ${accFmt}% · ${fmtTime(props.durationMs)}`;\n return (\n <Box flexDirection=\"column\">\n <Row\n left={<Text color={PALETTE.success}>{line}</Text>}\n right={<Text color={PALETTE.muted}>{t.stealth.nextHintRight}</Text>}\n />\n <Row left={<Text> </Text>} right={<Text color={PALETTE.muted}>Esc {t.common.back}</Text>} />\n <Row left={<Text> </Text>} right={<Text> </Text>} />\n </Box>\n );\n}\n"],"mappings":"sfAAA,OAAS,YAAAA,EAAU,aAAAC,EAAW,UAAAC,MAAc,QAC5C,OAAS,OAAAC,EAAK,QAAAC,EAAM,UAAAC,GAAQ,YAAAC,MAAgB,MCDrC,SAASC,GAAWC,EAAmBC,EAAoB,KAAK,OAAa,CAClF,IAAMC,EAAM,CAAC,GAAGF,CAAG,EACnB,QAASG,EAAID,EAAI,OAAS,EAAGC,EAAI,EAAGA,IAAK,CACvC,IAAMC,EAAI,KAAK,MAAMH,EAAI,GAAKE,EAAI,EAAE,EAC9BE,EAAMH,EAAIC,CAAC,EACjBD,EAAIC,CAAC,EAAID,EAAIE,CAAC,EACdF,EAAIE,CAAC,EAAIC,CACX,CACA,OAAOH,CACT,CAEO,SAASI,GAAWC,EAA4B,CACrD,IAAIC,EAAID,IAAS,EACjB,MAAO,IAAM,CACXC,EAAKA,EAAI,aAAgB,EACzB,IAAI,EAAI,KAAK,KAAKA,EAAKA,IAAM,GAAK,EAAIA,CAAC,EACvC,SAAK,EAAI,KAAK,KAAK,EAAK,IAAM,EAAI,GAAK,CAAC,EAAK,IACpC,EAAK,IAAM,MAAS,GAAK,UACpC,CACF,CCdO,SAASC,GAAcC,EAAeC,EAA+B,CAC1E,GAAIA,GAAe,EAAG,MAAM,IAAI,MAAM,8BAA8B,EACpE,IAAMC,EAAmB,CAAC,EAC1B,QAASC,EAAI,EAAGA,EAAIH,EAAM,OAAQG,GAAKF,EACrCC,EAAO,KAAKF,EAAM,MAAMG,EAAGA,EAAIF,CAAW,CAAC,EAE7C,OAAOC,CACT,CAcO,SAASE,GAAcC,EAAiBC,EAAYC,EAAuB,CAChF,GAAID,IAAS,SAAU,CACrB,IAAME,EAAMD,IAAS,OAAY,KAAK,OAASE,GAAWF,CAAI,EAC9D,OAAOG,GAAQL,EAASG,CAAG,CAC7B,CACA,OAAOH,CACT,CCnBO,SAASM,EAAaC,EAA4B,CACvD,MAAO,CAAE,OAAAA,EAAQ,MAAO,GAAI,eAAgB,CAAE,CAChD,CAEO,SAASC,GAAOC,EAAmBC,EAA4D,CACpG,OAAQA,EAAG,KAAM,CACf,IAAK,QACH,MAAO,CAAE,MAAO,CAAE,GAAGD,EAAO,MAAO,EAAG,EAAG,OAAQ,MAAO,EAC1D,IAAK,YACH,OAAIA,EAAM,MAAM,SAAW,EAAU,CAAE,MAAAA,EAAO,OAAQ,MAAO,EACtD,CAAE,MAAO,CAAE,GAAGA,EAAO,MAAOA,EAAM,MAAM,MAAM,EAAG,EAAE,CAAE,EAAG,OAAQ,MAAO,EAEhF,IAAK,OAAQ,CACX,IAAME,EAAYF,EAAM,MAAQC,EAAG,GAE7BE,EAAsB,CAAC,GAAGH,EAAM,MAAM,EAAE,MAAM,EAAG,CAAC,GAAGE,CAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EACrF,OAAIA,IAAcC,EACZD,EAAU,SAAWF,EAAM,OAAO,OAC7B,CAAE,MAAO,CAAE,GAAGA,EAAO,MAAOE,CAAU,EAAG,OAAQ,SAAU,EAE7D,CAAE,MAAO,CAAE,GAAGF,EAAO,MAAOE,CAAU,EAAG,OAAQ,UAAW,EAE9D,CACL,MAAO,CAAE,GAAGF,EAAO,MAAO,GAAI,eAAgBA,EAAM,eAAiB,CAAE,EACvE,OAAQ,OACV,CACF,CACF,CACF,CC5BO,SAASI,EAAaC,EAAkBC,EAAM,KAAK,IAAI,EAAY,CACxE,OAAID,EAAS,SAAW,EACf,CAAE,UAAWC,EAAK,QAAS,CAAC,EAAG,QAAS,KAAM,WAAYA,EAAK,SAAAD,CAAS,EAE1E,CACL,UAAWC,EACX,QAAS,CAAC,EACV,QAAS,CAAE,UAAW,EAAG,cAAeA,EAAK,MAAOC,EAAaF,EAAS,CAAC,EAAG,IAAI,CAAE,EACpF,WAAY,KACZ,SAAAA,CACF,CACF,CAEO,SAASG,EAAYC,EAAkBC,EAAgBJ,EAAM,KAAK,IAAI,EAA8C,CACzH,GAAI,CAACG,EAAQ,QAAS,MAAO,CAAE,QAAAA,EAAS,OAAQ,MAAO,EACvD,GAAM,CAAE,MAAAE,EAAO,OAAAC,CAAO,EAAIC,GAAOJ,EAAQ,QAAQ,MAAOC,CAAE,EAC1D,GAAIE,IAAW,UAAW,CACxB,IAAME,EAA8B,CAClC,KAAMH,EAAM,OACZ,OAAQA,EAAM,eACd,WAAYL,EAAMG,EAAQ,QAAQ,aACpC,EACMM,EAAYN,EAAQ,QAAQ,UAAY,EACxCO,EAAU,CAAC,GAAGP,EAAQ,QAASK,CAAQ,EAC7C,OAAIC,GAAaN,EAAQ,SAAS,OACzB,CACL,QAAS,CAAE,GAAGA,EAAS,QAAAO,EAAS,QAAS,KAAM,WAAYV,CAAI,EAC/D,OAAAM,CACF,EAEK,CACL,QAAS,CACP,GAAGH,EACH,QAAAO,EACA,QAAS,CACP,UAAWD,EACX,cAAeT,EACf,MAAOC,EAAaE,EAAQ,SAASM,CAAS,EAAG,IAAI,CACvD,CACF,EACA,OAAAH,CACF,CACF,CACA,MAAO,CACL,QAAS,CACP,GAAGH,EACH,QAAS,CAAE,GAAGA,EAAQ,QAAS,MAAOE,CAAM,CAC9C,EACA,OAAAC,CACF,CACF,CAEO,SAASK,GAAYR,EAAkBH,EAAM,KAAK,IAAI,EAA8C,CACzG,GAAI,CAACG,EAAQ,QAAS,MAAO,CAAE,QAAAA,EAAS,OAAQ,MAAO,EACvD,IAAMS,EAA4B,CAChC,KAAMT,EAAQ,QAAQ,MAAM,OAC5B,OAAQ,EACR,WAAYH,EAAMG,EAAQ,QAAQ,cAClC,QAAS,EACX,EACMM,EAAYN,EAAQ,QAAQ,UAAY,EACxCO,EAAU,CAAC,GAAGP,EAAQ,QAASS,CAAM,EAC3C,OAAIH,GAAaN,EAAQ,SAAS,OACzB,CACL,QAAS,CAAE,GAAGA,EAAS,QAAAO,EAAS,QAAS,KAAM,WAAYV,CAAI,EAC/D,OAAQ,SACV,EAEK,CACL,QAAS,CACP,GAAGG,EACH,QAAAO,EACA,QAAS,CACP,UAAWD,EACX,cAAeT,EACf,MAAOC,EAAaE,EAAQ,SAASM,CAAS,EAAG,IAAI,CACvD,CACF,EACA,OAAQ,SACV,CACF,CAEO,SAASI,EAAeV,EAK7B,CACA,IAAMW,EAASX,EAAQ,QAAQ,OAAO,CAACY,EAAGC,IAAMD,EAAIC,EAAE,OAAQ,CAAC,EACzDC,GACHd,EAAQ,YAAc,KAAK,IAAI,GAAKA,EAAQ,UACzCe,EAAwC,CAAC,EAC/C,QAAWF,KAAKb,EAAQ,QAClBa,EAAE,OAAS,IAAGE,EAAcF,EAAE,IAAI,GAAKE,EAAcF,EAAE,IAAI,GAAK,GAAKA,EAAE,QAE7E,MAAO,CAAE,UAAWb,EAAQ,QAAQ,OAAQ,OAAAW,EAAQ,WAAAG,EAAY,cAAAC,CAAc,CAChF,CC7GA,OAAS,aAAAC,GAAW,cAAAC,GAAY,UAAAC,GAAQ,YAAAC,OAAgB,QACxD,OAAS,YAAAC,GAAU,UAAAC,OAAc,MAUjC,SAASC,GAAQC,EAA6DC,EAAgB,CAC5F,GAAIA,EAAO,OAAS,QAClB,MAAO,CAAE,QAASC,EAAaD,EAAO,SAAUA,EAAO,GAAG,EAAG,WAAY,IAAK,EAEhF,GAAIA,EAAO,OAAS,OAAQ,CAC1B,IAAM,EAAIE,GAAYH,EAAM,QAASC,EAAO,GAAG,EAC/C,MAAO,CAAE,QAAS,EAAE,QAAS,WAAY,EAAE,MAAO,CACpD,CACA,GAAIA,EAAO,OAAS,QAAS,CAC3B,GAAIA,EAAO,IAAI,WAAaA,EAAO,IAAI,OAAQ,CAC7C,IAAMG,EAAIC,EAAYL,EAAM,QAAS,CAAE,KAAM,WAAY,EAAGC,EAAO,GAAG,EACtE,MAAO,CAAE,QAASG,EAAE,QAAS,WAAYA,EAAE,MAAO,CACpD,CACA,GAAIH,EAAO,MAAM,SAAW,EAAG,OAAOD,EACtC,IAAIM,EAAUN,EAAM,QAChBO,EAAiCP,EAAM,WAC3C,QAAW,KAAKC,EAAO,MAAO,CAC5B,IAAMG,EAAIC,EAAYC,EAAS,CAAE,KAAM,OAAQ,GAAI,CAAE,EAAGL,EAAO,GAAG,EAGlE,GAFAK,EAAUF,EAAE,QACZG,EAAaH,EAAE,OACXE,EAAQ,aAAe,KAAM,KACnC,CACA,MAAO,CAAE,QAAAA,EAAS,WAAAC,CAAW,CAC/B,CACA,OAAOP,CACT,CASO,SAASQ,GAAmBC,EAA0D,CAC3F,IAAMC,EAAQ,CAAC,GAAGD,CAAK,EACvB,GAAIC,EAAM,KAAMC,GAAMA,EAAE,YAAY,CAAC,GAAM,GAAI,EAC7C,MAAO,CAAE,KAAM,MAAO,QAAS,EAAG,EAEpC,IAAMC,EAAUF,EACb,OAAQC,GAAM,CACb,IAAME,EAAKF,EAAE,YAAY,CAAC,EAC1B,OAAOE,GAAM,IAAQA,GAAM,GAC7B,CAAC,EACA,KAAK,EAAE,EACV,MAAO,CAAE,KAAMD,EAAQ,OAAS,EAAI,QAAU,QAAS,QAAAA,CAAQ,CACjE,CAmBO,SAASE,GAAY,CAC1B,SAAAC,EACA,WAAAC,EACA,MAAAC,EACA,SAAAC,EACA,OAAAC,EACA,WAAAC,EACA,aAAAC,EACA,QAAAC,EAAU,EACZ,EAAoB,CAClB,GAAM,CAACtB,EAAOuB,CAAQ,EAAIC,GAAWzB,GAAS,OAAW,KAAO,CAC9D,QAASG,EAAaa,EAAU,KAAK,IAAI,CAAC,EAC1C,WAAY,IACd,EAAE,EACIU,EAAeC,GAAO,EAAK,EAC3B,CAACC,EAAMC,CAAO,EAAIC,GAAS,CAAC,EAC5B,CAAE,KAAAC,CAAK,EAAIC,GAAO,EAExB,OAAAC,GACE,CAACvB,EAAOwB,IAAQ,CACd,GAAIA,EAAI,MAAQxB,IAAU,IAAK,CAC7BqB,EAAK,EACL,MACF,CACA,GAAIG,EAAI,MAAQxB,IAAU,IAAK,CAC7BU,IAAS,EACTI,EAAS,CAAE,KAAM,OAAQ,IAAK,KAAK,IAAI,CAAE,CAAC,EAC1C,MACF,CACA,GAAIU,EAAI,OAAQ,CACdf,IAAW,EACX,MACF,CACA,GAAIe,EAAI,IAAK,CACXhB,IAAQ,EACR,MACF,CAEA,GADIgB,EAAI,SAAWA,EAAI,WAAaA,EAAI,WAAaA,EAAI,YAAcA,EAAI,QACvEA,EAAI,MAAQA,EAAI,KAAM,OAC1B,GAAM,CAAE,KAAAC,EAAM,QAAAtB,CAAQ,EAAIJ,GAAmBC,CAAK,EAClD,GAAIyB,IAAS,MAAO,CAClBd,IAAa,EACb,MACF,CACIc,IAAS,UACbb,IAAe,EACfE,EAAS,CAAE,KAAM,QAAS,MAAOX,EAAS,IAAAqB,EAAK,IAAK,KAAK,IAAI,CAAE,CAAC,EAClE,EACA,CAAE,SAAUX,CAAQ,CACtB,EAEAa,GAAU,IAAM,CACVnC,EAAM,QAAQ,aAAe,MAAQ,CAACyB,EAAa,UACrDA,EAAa,QAAU,GACvBT,EAAWhB,EAAM,OAAO,EAE5B,EAAG,CAACA,EAAM,QAASgB,CAAU,CAAC,EAE9BmB,GAAU,IAAM,CACd,GAAInC,EAAM,QAAQ,aAAe,KAAM,OACvC,IAAMoC,EAAK,YAAY,IAAMR,EAASS,GAAMA,EAAI,CAAC,EAAG,GAAI,EACxD,MAAO,IAAM,cAAcD,CAAE,CAC/B,EAAG,CAACpC,EAAM,QAAQ,UAAU,CAAC,EAEtB,CAAE,QAASA,EAAM,QAAS,WAAYA,EAAM,WAAY,KAAA2B,CAAK,CACtE,CC7IA,OAAS,aAAAW,GAAW,UAAAC,OAAc,QAwB3B,SAASC,GAASC,EAA2B,CAClD,IAAMC,EAAYC,GAAO,EAAK,EAC9B,OAAAC,GAAU,IAAM,CACVF,EAAU,UACdA,EAAU,QAAU,GACpBG,GAAU,CAACJ,EAAK,OAAO,EAAE,MAAM,IAAG,EAAY,EAChD,EAAG,CAACA,EAAK,OAAO,CAAC,EAEV,CACL,UAAW,IAAMA,EAAK,SAAWK,GAAc,EAC/C,QAAS,IAAML,EAAK,SAAWM,GAAY,EAC3C,MAAO,IAAMN,EAAK,SAAWO,GAAU,EACvC,UAAYC,GAAS,CACdR,EAAK,SACNA,EAAK,uBAA4BS,GAAkBD,EAAMR,EAAK,MAAM,CAC1E,EACA,SAAWQ,GAAS,CACbR,EAAK,SACLU,GAAsBF,EAAMR,EAAK,MAAM,CAC9C,CACF,CACF,CC7CA,OAAS,eAAAW,OAAmB,QAarB,SAASC,GAAsBC,EAA4D,CAChG,OAAOC,GACL,MAAOC,GAAoC,CACzC,IAAMC,EAAqB,CACzB,GAAI,IAAI,KAAK,EAAE,YAAY,EAC3B,OAAQH,EAAK,OACb,QAASA,EAAK,aACd,KAAMA,EAAK,KACX,UAAWE,EAAQ,UACnB,OAAQA,EAAQ,OAChB,WAAYA,EAAQ,WACpB,cAAeA,EAAQ,aACzB,EACA,MAAME,GAAcD,CAAG,EACvBE,GAAa,CACX,OAAQL,EAAK,OACb,aAAcA,EAAK,aACnB,KAAMA,EAAK,KACX,UAAWE,EAAQ,UACnB,OAAQA,EAAQ,OAChB,WAAYA,EAAQ,WACpB,cAAeA,EAAQ,aACzB,CAAC,EACD,IAAMI,EAAQ,OAAO,QAAQJ,EAAQ,aAAa,EAAE,OAAO,CAAC,CAAC,CAAEK,CAAC,IAAMA,EAAI,CAAC,EAC3E,GAAID,EAAM,SAAW,EAAG,OACxB,IAAIE,EAAO,MAAMC,EAAa,EAC9B,OAAW,CAACC,EAAMH,CAAC,IAAKD,EAAOE,EAAOG,GAAKH,EAAME,EAAMV,EAAK,OAAQO,CAAC,EACrE,MAAMK,GAAaJ,CAAI,CACzB,EACA,CAACR,EAAK,OAAQA,EAAK,aAAcA,EAAK,IAAI,CAC5C,CACF,CC3CA,OAAS,OAAAa,EAAK,QAAAC,EAAM,aAAAC,OAAiB,MAsBjC,OAgDI,YAAAC,GA/CF,OAAAC,EADF,QAAAC,MAAA,oBAlBJ,IAAMC,GAAc,GAEpB,SAASC,GAAQC,EAAoB,CACnC,IAAMC,EAAQ,KAAK,MAAMD,EAAK,GAAI,EAC5BE,EAAI,KAAK,MAAMD,EAAQ,EAAE,EACzBE,EAAIF,EAAQ,GAClB,MAAO,GAAGC,CAAC,IAAI,OAAOC,CAAC,EAAE,SAAS,EAAG,GAAG,CAAC,EAC3C,CAEA,SAASC,IAAuB,CAC9B,GAAM,CAAE,OAAAC,CAAO,EAAIC,GAAU,EACvBC,EAAOF,GAAQ,SAAW,GAChC,OAAO,KAAK,IAAI,GAAIE,EAAOT,EAAW,CACxC,CAEA,SAASU,EAAI,CAAE,KAAAC,EAAM,MAAAC,CAAM,EAA0C,CACnE,IAAMC,EAAYP,GAAa,EAC/B,OACEP,EAACe,EAAA,CACC,UAAAhB,EAACgB,EAAA,CAAI,MAAOD,EAAY,SAAAF,EAAK,EAC7Bb,EAACgB,EAAA,CAAI,MAAOd,GAAa,eAAe,WACrC,SAAAY,EACH,GACF,CAEJ,CAEO,SAASG,GAAcC,EAmB3B,CACD,IAAMC,EAAIC,EAAW,EACfC,EAAS,CAAC,GAAGH,EAAM,MAAM,EACzBI,EAAQ,CAAC,GAAGJ,EAAM,KAAK,EAIvBK,EACJtB,EAACe,EAAA,CACE,UAAAK,EAAO,IAAI,CAACG,EAAIC,IAAM,CACrB,IAAMC,EAAUD,EAAIH,EAAM,OACpBK,EAAUT,EAAM,YAAc,CAACQ,EAAU,IAAMA,EAAUJ,EAAMG,CAAC,EAAKD,EACrEI,EAAQV,EAAM,MAAQW,EAAQ,MAAQH,EAAUG,EAAQ,OAASA,EAAQ,MAC/E,OACE7B,EAAC8B,EAAA,CAAa,KAAI,GAAC,MAAOF,EACvB,SAAAD,GADQF,CAEX,CAEJ,CAAC,EACAP,EAAM,UACLjB,EAAAF,GAAA,CACE,UAAAC,EAAC8B,EAAA,CAAK,cAAE,EACR9B,EAAC8B,EAAA,CAAK,OAAM,GAAC,SAAQ,GAAC,MAAOD,EAAQ,MAClC,SAAAX,EAAM,SACT,GACF,GAEJ,EAIIa,EAAkBb,EAAM,YAAY,OAAS,EACjDlB,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,QAAU,SAAAX,EAAM,YAAY,CAAC,EAAG,EAErDlB,EAAC8B,EAAA,CAAK,aAAC,EAGHE,EAAOd,EAAM,KACbe,EAAS,OAAO,UAAUD,EAAK,MAAM,EAAI,GAAGA,EAAK,MAAM,GAAKA,EAAK,OAAO,QAAQ,CAAC,EAOjFE,EAAY,CAAChB,EAAM,YAAcA,EAAM,eAAiB,KACxDiB,EAASjB,EAAM,WACnBlB,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,QAAU,SAAAV,EAAE,SAAS,gBAAgB,EACxDe,EACFlC,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,QAAU,SAAAV,EAAE,SAAS,kBAAkB,EAC1Da,EAAK,QACPhC,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,MAAQ,YAAGG,EAAK,QAAQ,SAAMA,EAAK,YAAY,GAAG,EAEvEhC,EAAC8B,EAAA,CAAK,aAAC,EAEHM,EAASlB,EAAM,YAAcgB,EACjClC,EAAC8B,EAAA,CAAK,aAAC,EACLE,EAAK,QACPhC,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,MAAQ,YAAGG,EAAK,SAAS,IAAIA,EAAK,KAAK,SAAMA,EAAK,GAAG,YAASC,CAAM,IAAI,EAE7FjC,EAAC8B,EAAA,CAAK,aAAC,EAEHO,EAASnB,EAAM,YAAcgB,EACjClC,EAAC8B,EAAA,CAAK,aAAC,EACLE,EAAK,QACPhC,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,MAAQ,SAAA1B,GAAQ6B,EAAK,SAAS,EAAE,EAErDhC,EAAC8B,EAAA,CAAK,aAAC,EAMT,OACE7B,EAACe,EAAA,CAAI,cAAc,SACjB,UAAAhB,EAACY,EAAA,CAAI,KAAMW,EAAU,MAAOY,EAAQ,EACpCnC,EAACY,EAAA,CAAI,KAAMZ,EAAC8B,EAAA,CAAK,aAAC,EAAS,MAAOM,EAAQ,EAC1CpC,EAACY,EAAA,CAAI,KAAMmB,EAAiB,MAAOM,EAAQ,GAC7C,CAEJ,CAEO,SAASC,IAAgB,CAC9B,IAAM,EAAIlB,EAAW,EACrB,OACEnB,EAACe,EAAA,CAAI,cAAc,SACjB,UAAAhB,EAACY,EAAA,CACC,KAAMZ,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,QAAU,WAAE,QAAQ,OAAO,EACtD,MAAO7B,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,MAAQ,WAAE,QAAQ,gBAAgB,EAChE,EACA7B,EAACY,EAAA,CAAI,KAAMZ,EAAC8B,EAAA,CAAK,aAAC,EAAS,MAAO7B,EAAC6B,EAAA,CAAK,MAAOD,EAAQ,MAAO,iBAAK,EAAE,OAAO,MAAK,EAAS,EAC1F7B,EAACY,EAAA,CAAI,KAAMZ,EAAC8B,EAAA,CAAK,aAAC,EAAS,MAAO9B,EAAC8B,EAAA,CAAK,aAAC,EAAS,GACpD,CAEJ,CAEO,SAASS,GAAerB,EAM5B,CACD,IAAMC,EAAIC,EAAW,EACfa,EAAS,OAAO,UAAUf,EAAM,MAAM,EAAI,GAAGA,EAAM,MAAM,GAAKA,EAAM,OAAO,QAAQ,CAAC,EACpFsB,EAAO,GAAGrB,EAAE,QAAQ,WAAW,SAAMD,EAAM,SAAS,UAAOA,EAAM,GAAG,YAASe,CAAM,UAAO9B,GAAQe,EAAM,UAAU,CAAC,GACzH,OACEjB,EAACe,EAAA,CAAI,cAAc,SACjB,UAAAhB,EAACY,EAAA,CACC,KAAMZ,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,QAAU,SAAAW,EAAK,EAC1C,MAAOxC,EAAC8B,EAAA,CAAK,MAAOD,EAAQ,MAAQ,SAAAV,EAAE,QAAQ,cAAc,EAC9D,EACAnB,EAACY,EAAA,CAAI,KAAMZ,EAAC8B,EAAA,CAAK,aAAC,EAAS,MAAO7B,EAAC6B,EAAA,CAAK,MAAOD,EAAQ,MAAO,iBAAKV,EAAE,OAAO,MAAK,EAAS,EAC1FnB,EAACY,EAAA,CAAI,KAAMZ,EAAC8B,EAAA,CAAK,aAAC,EAAS,MAAO9B,EAAC8B,EAAA,CAAK,aAAC,EAAS,GACpD,CAEJ,CRvFW,cAAAW,EAoVL,QAAAC,MApVK,oBArDJ,SAASC,GAAe,CAAE,OAAAC,CAAO,EAA+B,CACrE,GAAM,CAAE,OAAAC,EAAQ,aAAAC,EAAc,KAAAC,CAAK,EAAIH,EACjC,CAAE,IAAAI,CAAI,EAAIC,EAAY,EACtBC,EAAIC,EAAW,EAEf,CAACC,EAAOC,CAAQ,EAAIC,EAAgB,SAAS,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAwB,IAAI,EAClD,CAACG,EAAUC,CAAW,EAAIJ,EAAwB,IAAI,EA6C5D,OA3CAK,EAAU,IAAM,CACd,IAAIC,EAAY,GAChB,OAAAP,EAAS,SAAS,EAClBG,EAAU,IAAI,EACdE,EAAY,IAAI,GACf,SAAY,CACX,GAAI,CACF,IAAMG,EAAQ,MAAMC,GAAiBjB,CAAM,EAC3C,GAAIe,EAAW,OACf,GAAIb,IAAS,SAAU,CACrB,IAAMgB,EAAO,MAAMC,EAAa,EAChC,GAAIJ,EAAW,OACf,IAAMK,EAAcJ,EAAM,OAAQK,GAAMH,EAAKG,EAAE,IAAI,GAAG,KAAK,EAAE,MAAM,EAAGlB,EAAI,WAAW,EACrF,GAAIiB,EAAY,SAAW,EAAG,CAC5BP,EAAYR,EAAE,SAAS,OAAO,UAAU,EACxCG,EAAS,OAAO,EAChB,MACF,CACAG,EAAU,CAAE,SAAUS,EAAa,cAAe,CAAE,CAAC,EACrDZ,EAAS,QAAQ,EACjB,MACF,CACA,IAAMc,EAAWC,GAAcP,EAAOb,EAAI,WAAW,EACrD,GAAImB,EAAS,SAAW,EAAG,CACzBT,EAAYR,EAAE,SAAS,OAAO,UAAUL,CAAM,CAAC,EAC/CQ,EAAS,OAAO,EAChB,MACF,CACA,IAAMgB,EAAM,KAAK,IAAI,EAAG,KAAK,IAAIF,EAAS,OAAS,EAAGrB,CAAY,CAAC,EAC7DwB,EAAWC,GAAcJ,EAASE,CAAG,EAAItB,CAAI,EACnDS,EAAU,CAAE,SAAAc,EAAU,cAAeH,EAAS,MAAO,CAAC,EACtDd,EAAS,QAAQ,CACnB,OAASmB,EAAK,CACZ,GAAIZ,EAAW,OACfF,EAAac,EAAc,OAAO,EAClCnB,EAAS,OAAO,CAClB,CACF,GAAG,EACI,IAAM,CACXO,EAAY,EACd,CACF,EAAG,CAACf,EAAQC,EAAcC,EAAMC,EAAI,YAAaE,CAAC,CAAC,EAE/CE,IAAU,UACLX,EAACgC,GAAA,CAAS,KAAMvB,EAAE,SAAS,QAAS,MAAOwB,EAAQ,MAAO,EAE/DtB,IAAU,QACLX,EAACkC,GAAA,CAAU,IAAKlB,GAAYP,EAAE,SAAS,OAAO,QAAS,EAE3DK,EAGHd,EAACmC,GAAA,CAEC,OAAQhC,EACR,OAAQW,EACR,MAAOH,EACP,SAAUC,GAJL,GAAGR,CAAM,IAAIC,CAAY,IAAIC,CAAI,IAAIH,EAAO,QAAU,IAAM,GAAG,EAKtE,EATkB,IAWtB,CAEA,SAASgC,GAAe,CACtB,OAAAhC,EACA,OAAAW,EACA,MAAAH,EACA,SAAAC,CACF,EAKG,CACD,GAAM,CAAE,OAAAR,EAAQ,aAAAC,EAAc,KAAAC,CAAK,EAAIH,EACjCiC,EAAUjC,EAAO,UAAY,GAC7B,CAAE,IAAAI,CAAI,EAAIC,EAAY,EACtB6B,EAAMC,EAAO,EACb,CAAE,KAAAC,CAAK,EAAIC,GAAO,EAClBC,EAAS,IAAOJ,EAAI,MAAM,OAAS,EAAIA,EAAI,KAAK,EAAIE,EAAK,EACzDG,EAAUC,GAAsB,CAAE,OAAAvC,EAAQ,aAAAC,EAAc,KAAAC,CAAK,CAAC,EAC9DsC,EAAWC,GAAYzC,CAAM,EAE7B0C,EAAQC,GAAS,CACrB,QAAS,CAACX,GAAW7B,EAAI,OAAO,OAChC,OAAQA,EAAI,OACZ,sBAAuB,CAAC6B,GAAW7B,EAAI,qBACzC,CAAC,EACKyC,EAAcC,GAAe,EAG7BC,EAAY3C,EAAI,OAAO,OAASyC,EAAY,QAAU,KAEtDG,EAAcC,EAAO,EAAK,EAC1BC,EAAgBD,EAAsB,IAAI,EAC1CE,EAAeF,EAAe,EAAE,EAChC,CAACG,GAAaC,EAAc,EAAI3C,EAAS,EAAK,EAC9C,CAAC4C,GAAaC,EAAc,EAAI7C,EAAwB,IAAI,EAC5D,CAAC8C,GAAYC,EAAa,EAAI/C,EAAS,EAAK,EAElDK,EAAU,IAAM,CACd,GAAIuC,KAAgB,KAAM,OAC1B,IAAMI,EAAK,WAAW,IAAML,GAAe,EAAK,EAAG,GAAI,EACvD,MAAO,IAAM,aAAaK,CAAE,CAC9B,EAAG,CAACJ,EAAW,CAAC,EAEhB,GAAM,CAAE,QAAAK,EAAS,WAAAC,EAAY,KAAAC,EAAK,EAAIC,GAAY,CAChD,SAAUnD,EAAO,SACjB,QAASH,IAAU,SACnB,WAAauD,GAAM,CACbf,EAAY,UAChBA,EAAY,QAAU,GACtBvC,EAAS,SAAS,EAClB,QAAQ,QAAQ8B,EAAQyB,EAAeD,CAAC,CAAC,CAAC,EAAE,MAAOnC,GAAQ,CACzD,QAAQ,MAAM,6BAA8BA,CAAG,CACjD,CAAC,EACH,EACA,SAAU,IAAMnB,EAASD,IAAU,SAAW,SAAW,QAAQ,EACjE,MAAOyB,EACH,OACA,IAAM,CACJ,IAAMgC,EAAMN,EAAQ,QAAUhD,EAAO,SAASgD,EAAQ,QAAQ,SAAS,EAAI,OACvEM,GAAUtB,EAAM,UAAUsB,EAAI,IAAI,CACxC,EACJ,WAAY,IAAMR,GAAc,EAAI,EACpC,aAAc,IAAMA,GAAc,EAAK,CACzC,CAAC,EAED1C,EAAU,IAAM,CACVkB,GACA2B,IAAe,MACfA,IAAeV,EAAc,UACjCA,EAAc,QAAUU,EACpBA,IAAe,SAAWxD,EAAI,OAAO,UAAUuC,EAAM,MAAM,EAC3DiB,IAAe,YAAcxD,EAAI,OAAO,WAAWuC,EAAM,UAAU,EACnEiB,IAAe,YACbxD,EAAI,OAAO,UAAUuC,EAAM,QAAQ,EACnCvC,EAAI,OAAO,WAAWuC,EAAM,UAAU,GAE9C,EAAG,CAACV,EAAS2B,EAAYjB,EAAOvC,EAAI,OAAO,SAAUA,EAAI,OAAO,SAAS,CAAC,EAE1EW,EAAU,IAAM,CACd,GAAIkB,EAAS,OACb,IAAMR,EAAMkC,EAAQ,SAAS,WAAa,GAE1C,GADIlC,IAAQ,IACRA,IAAQ0B,EAAa,QAAS,OAClCA,EAAa,QAAU1B,EACvB,IAAMwC,EAAMtD,EAAO,SAASc,CAAG,EACzByC,EAAOvD,EAAO,SAASc,EAAM,CAAC,EAChCwC,GAAO7D,EAAI,uBAAuBuC,EAAM,UAAUsB,EAAI,IAAI,EAC1DC,GAAMvB,EAAM,SAASuB,EAAK,IAAI,CACpC,EAAG,CAACjC,EAAS0B,EAAQ,SAAS,UAAWhB,EAAOvC,EAAI,sBAAuBO,EAAO,QAAQ,CAAC,EAI3FwD,EACE,CAACC,EAAQC,IAAQ,CAKf,GAAIA,EAAI,IAAK,CACXhB,GAAe,EAAI,EACnBE,GAAe,KAAK,IAAI,CAAC,EACzB,MACF,CACF,EACA,CAAE,SAAUtB,GAAWzB,IAAU,QAAS,CAC5C,EAEA2D,EACE,CAACC,EAAQC,IAAQ,CACf,GAAIA,EAAI,OAAQ,CACd5D,EAAS,QAAQ,EACjB,MACF,CACA,GAAI4D,EAAI,OAAQ,CACd/B,EAAO,EACP,MACF,CACF,EACA,CAAE,SAAU9B,IAAU,QAAS,CACjC,EAEA2D,EACE,CAACG,EAAOD,IAAQ,CACd,GAAIA,EAAI,OAAQ,CACd/B,EAAO,EACP,MACF,CACA,GAAI+B,EAAI,OAAQ,CACd,IAAME,EAAUrE,EAAe,EAC3BC,IAAS,OACX+B,EAAI,QAAQ,CACV,KAAM,WACN,OAAQ,CAAE,OAAAjC,EAAQ,aAAAC,EAAc,KAAAC,EAAM,QAASH,EAAO,OAAQ,CAChE,CAAC,EACQG,IAAS,UAAYoE,GAAW5D,EAAO,cAChD2B,EAAO,EAEPJ,EAAI,QAAQ,CACV,KAAM,WACN,OAAQ,CAAE,OAAAjC,EAAQ,aAAcsE,EAAS,KAAApE,EAAM,QAASH,EAAO,OAAQ,CACzE,CAAC,EAEH,MACF,CACA,GAAIsE,IAAU,IAAK,CACjBpC,EAAI,QAAQ,CACV,KAAM,WACN,OAAQ,CAAE,OAAAjC,EAAQ,aAAc,EAAG,KAAM,SAAU,QAASD,EAAO,OAAQ,CAC7E,CAAC,EACD,MACF,CACF,EACA,CAAE,SAAUQ,IAAU,SAAU,CAClC,EAEA,IAAMgE,EAAYb,EAAQ,QAAQ,OAC5Bc,GAASd,EAAQ,QAAQ,OAAO,CAACe,EAAGC,IAAMD,EAAIC,EAAE,OAAQ,CAAC,EACzDC,EAAY,KAAK,IAAI,EAAIjB,EAAQ,UACjCkB,GAAUD,EAAY,IACtBE,GAAMD,GAAU,EAAI,KAAK,MAAOL,EAAYK,GAAW,EAAE,EAAI,GAAK,EAElEE,EAAUvE,IAAU,UAAYwD,EAAeL,CAAO,EAAI,KAEhE,GAAI1B,EAAS,CACX,GAAIzB,IAAU,SAAU,OAAOX,EAACmF,GAAA,EAAc,EAC9C,GAAIxE,IAAU,WAAauE,EAAS,CAClC,IAAME,EAAWF,EAAQ,WAAa,IAChCG,GAAOD,EAAW,EAAI,KAAK,MAAOF,EAAQ,UAAYE,EAAY,EAAE,EAAI,GAAK,EAC7EE,GAAY,OAAO,KAAKJ,EAAQ,aAAa,EAAE,OAC/CK,GACJL,EAAQ,YAAc,EAAI,EAAI,KAAK,IAAI,GAAIA,EAAQ,UAAYI,IAAaJ,EAAQ,SAAS,EACzFM,GAAU,KAAK,MAAMD,GAAO,GAAI,EAAI,GAC1C,OACEvF,EAACyF,GAAA,CACC,UAAWP,EAAQ,UACnB,OAAQA,EAAQ,OAChB,WAAYA,EAAQ,WACpB,IAAKG,GACL,OAAQG,GACV,CAEJ,CACA,IAAME,EAAc5B,EAAQ,QACxBhD,EAAO,SAASgD,EAAQ,QAAQ,SAAS,EACzChD,EAAO,SAASA,EAAO,SAAS,OAAS,CAAC,EACxC6E,EAAa7B,EAAQ,SAAS,OAAS,CAAE,OAAQ,GAAI,MAAO,GAAI,eAAgB,CAAE,EAClF8B,EAAW,IAAI,IACnB9B,EAAQ,QAAQ,OAAQgB,GAAMA,EAAE,OAAS,CAAC,EAAE,IAAKA,GAAMA,EAAE,IAAI,CAC/D,EAAE,KACIe,GACJlB,IAAc,EAAI,EAAI,KAAK,IAAI,GAAIA,EAAYiB,GAAYjB,CAAS,EAChEmB,GAAS,KAAK,MAAMD,GAAU,GAAI,EAAI,GACtCE,GACJzF,IAAS,SACL,SACA,MAAMD,EAAe,CAAC,IAAIS,EAAO,aAAa,GACpD,OACEd,EAACgG,GAAA,CACC,OAAQN,GAAa,MAAQ,GAC7B,MAAOC,EAAW,MAClB,WAAYrF,IAAS,YACrB,SAAU2F,GAAaP,EAAanF,EAAI,MAAM,EAC9C,YAAamF,GAAa,OAAS,CAAC,EACpC,MAAO3B,IAAe,QACtB,WAAYJ,GACZ,aAAcT,EACd,KAAM,CACJ,QAASK,GACT,SAAU2C,EAAatD,EAAU,EAAE,EACnC,aAAAmD,GACA,UAAApB,EACA,MAAO7D,EAAO,SAAS,OACvB,IAAAmE,GACA,OAAAa,GACA,UAAAf,CACF,EACF,CAEJ,CAEA,GAAIpE,IAAU,SACZ,OACEX,EAACmG,GAAA,CACC,SAAUvD,EACV,aAAcvC,EACd,cAAeS,EAAO,cACtB,KAAMR,EACN,UAAWqE,EACX,MAAO7D,EAAO,SAAS,OACzB,EAIJ,GAAIH,IAAU,WAAauE,EACzB,OACElF,EAACoG,GAAA,CACC,SAAUxD,EACV,aAAcvC,EACd,cAAeS,EAAO,cACtB,KAAMR,EACN,QAAS4E,EACX,EAIJ,IAAMQ,EAAc5B,EAAQ,QACxBhD,EAAO,SAASgD,EAAQ,QAAQ,SAAS,EACzChD,EAAO,SAASA,EAAO,SAAS,OAAS,CAAC,EACxC6E,GAAa7B,EAAQ,SAAS,OAAS,CAAE,OAAQ,GAAI,MAAO,GAAI,eAAgB,CAAE,EAExF,OACE9D,EAACqG,GAAA,CACC,SAAUzD,EACV,aAAcvC,EACd,cAAeS,EAAO,cACtB,KAAMR,EACN,OAAQC,EAAI,OACZ,UAAWoE,EACX,MAAO7D,EAAO,SAAS,OACvB,OAAQ8D,GACR,IAAKK,GACL,UAAWF,EACX,OAAQW,GAAa,MAAQ,GAC7B,MAAOC,GAAW,MAClB,WAAY5B,IAAe,QAC3B,WAAYzD,IAAS,YACrB,SAAU2F,GAAaP,EAAanF,EAAI,MAAM,EAC9C,YAAamF,GAAa,OAAS,CAAC,EACpC,WAAY/B,GACZ,aAAcT,EAChB,CAEJ,CAEA,SAAS+C,GAAaK,EAAwBC,EAAoC,CAChF,OAAKD,GACKC,IAAW,KAAOD,EAAK,QAAUA,EAAK,UACpC,KAFM,IAGpB,CAEA,SAASE,GAAQC,EAAoB,CACnC,IAAMC,EAAQ,KAAK,MAAMD,EAAK,GAAI,EAC5BE,EAAI,KAAK,MAAMD,EAAQ,EAAE,EACzBxC,EAAIwC,EAAQ,GAClB,MAAO,GAAGC,CAAC,IAAI,OAAOzC,CAAC,EAAE,SAAS,EAAG,GAAG,CAAC,EAC3C,CAEA,SAASmC,GAAaO,EAmBnB,CACD,IAAMnG,EAAIC,EAAW,EACfmG,EAAeD,EAAM,QAAU,EAAI,EAAIA,EAAM,UAAYA,EAAM,MACrE,OACE3G,EAAC6G,EAAA,CAAI,cAAc,SAAS,SAAU,EAAG,SAAU,EAAG,MAAM,OAAO,OAAO,OACxE,UAAA9G,EAAC+G,GAAA,CACC,SAAUH,EAAM,SAChB,aAAcA,EAAM,aACpB,cAAeA,EAAM,cACrB,KAAMA,EAAM,KACZ,OAAQA,EAAM,OACd,UAAWA,EAAM,UACjB,MAAOA,EAAM,MACb,UAAWA,EAAM,UACnB,EAEA3G,EAAC6G,EAAA,CAAI,SAAU,EAAG,cAAc,SAAS,WAAW,SAAS,eAAe,SAC1E,UAAA9G,EAACgH,GAAA,CACC,OAAQJ,EAAM,OACd,MAAOA,EAAM,MACb,MAAOA,EAAM,WACb,WAAYA,EAAM,WACpB,EAECA,EAAM,UACL5G,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,OAAM,GAAC,SAAQ,GAAC,MAAOhF,EAAQ,MAClC,SAAA2E,EAAM,SACT,EACF,EAGDA,EAAM,YAAY,OAAS,GAC1B5G,EAAC8G,EAAA,CAAI,UAAW,EAAG,cAAc,SAAS,WAAW,SAClD,SAAAF,EAAM,YAAY,MAAM,EAAG,CAAC,EAAE,IAAI,CAACM,EAAIC,IACtCnH,EAACiH,EAAA,CAAa,MAAOhF,EAAQ,QAC1B,SAAAiF,GADQC,CAEX,CACD,EACH,EAGDP,EAAM,YACL5G,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,QAAU,SAAAxB,EAAE,SAAS,WAAW,EACvD,EAGD,CAACmG,EAAM,YAAcA,EAAM,cAC1B5G,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,QAAU,SAAA2E,EAAM,aAAa,EACpD,GAEJ,EAEA3G,EAAC6G,EAAA,CAAI,cAAc,SACjB,UAAA9G,EAACoH,GAAA,CAAY,KAAMP,EAAc,EACjC7G,EAAC8G,EAAA,CAAI,eAAe,SAAS,UAAW,EACtC,SAAA7G,EAACgH,EAAA,CAAK,MAAOhF,EAAQ,MAClB,UAAA2E,EAAM,UAAU,IAAEA,EAAM,MAAM,WAAMJ,GAAQI,EAAM,SAAS,EAAE,WAAMA,EAAM,IAAI,IAAEnG,EAAE,SAAS,UAAU,IAAI,WAAMmG,EAAM,OAAO,IAAEnG,EAAE,SAAS,UAAU,QACrJ,EACF,EACAT,EAAC8G,EAAA,CAAI,eAAe,SAAS,UAAW,EACtC,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAxB,EAAE,SAAS,QAAQ,OAAO,EACzD,GACF,GACF,CAEJ,CAEA,SAASsG,GAAUH,EAShB,CACD,IAAMnG,EAAIC,EAAW,EACf2G,EAAW5G,EAAE,SAAS,MAAMmG,EAAM,IAAI,EACtCU,EAAa7G,EAAE,SAAS,QAAQmG,EAAM,MAAM,EAC5CW,EAAOrB,EAAaU,EAAM,SAAU,EAAE,EACtCY,EACJZ,EAAM,OAAS,SACX,GAAGW,CAAI,WAAQ9G,EAAE,SAAS,WAAW,WAAQ6G,CAAU,GACvD,GAAGC,CAAI,WAAQ9G,EAAE,SAAS,aAAamG,EAAM,aAAe,EAAGA,EAAM,aAAa,CAAC,WAAQS,CAAQ,WAAQC,CAAU,GACrHG,EAAQ,GAAGb,EAAM,SAAS,IAAIA,EAAM,KAAK,WAAQJ,GAAQI,EAAM,SAAS,CAAC,GAC/E,OACE3G,EAAC6G,EAAA,CACC,UAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAuF,EAAK,EAClCxH,EAAC8G,EAAA,CAAI,SAAU,EAAG,EAClB9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAwF,EAAM,GACrC,CAEJ,CAEA,SAASL,GAAY,CAAE,KAAAM,CAAK,EAAqB,CAC/C,IAAMC,EAAO,QAAQ,OAAO,SAAW,GACjCC,EAAQ,KAAK,IAAI,GAAI,KAAK,IAAI,GAAID,EAAO,EAAE,CAAC,EAC5CE,EAAS,KAAK,MAAMD,EAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGF,CAAI,CAAC,CAAC,EAC1DI,EAAQF,EAAQC,EACtB,OACE5H,EAAC6G,EAAA,CAAI,eAAe,SAClB,UAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,OAAS,kBAAI,OAAO4F,CAAM,EAAE,EACjD7H,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,kBAAI,OAAO6F,CAAK,EAAE,GACjD,CAEJ,CAEA,SAAS3B,GAAWS,EAOjB,CACD,IAAMnG,EAAIC,EAAW,EACfgH,EAAOd,EAAM,QAAU,EAAI,EAAIA,EAAM,UAAYA,EAAM,MACvDmB,EACJnB,EAAM,OAAS,SACX,GAAGV,EAAaU,EAAM,SAAU,EAAE,CAAC,WAAQnG,EAAE,SAAS,WAAW,GACjE,GAAGyF,EAAaU,EAAM,SAAU,EAAE,CAAC,WAAQnG,EAAE,SAAS,MAAM,QAAQmG,EAAM,aAAe,EAAGA,EAAM,aAAa,CAAC,GACtH,OACE3G,EAAC6G,EAAA,CAAI,cAAc,SAAS,WAAW,SAAS,eAAe,SAAS,MAAM,OAAO,OAAO,OAC1F,UAAA9G,EAACiH,EAAA,CAAK,KAAI,GAAC,MAAOhF,EAAQ,QACvB,SAAAxB,EAAE,SAAS,MAAM,MACpB,EACAT,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAA8F,EAAS,EACxC,EACA/H,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACoH,GAAA,CAAY,KAAMM,EAAM,EAC3B,EACA1H,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAxB,EAAE,SAAS,MAAM,SAASmG,EAAM,UAAWA,EAAM,KAAK,EAAE,EACvF,EACA5G,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAxB,EAAE,SAAS,MAAM,KAAK,EACrD,GACF,CAEJ,CAEA,SAASyB,GAAU,CAAE,IAAA8F,CAAI,EAAoB,CAC3C,IAAMvH,EAAIC,EAAW,EACrB,OACET,EAAC6G,EAAA,CAAI,cAAc,SAAS,WAAW,SAAS,eAAe,SAAS,MAAM,OAAO,OAAO,OAC1F,UAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAA+F,EAAI,EACjChI,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA7G,EAACgH,EAAA,CAAK,MAAOhF,EAAQ,MAAO,iBAAKxB,EAAE,OAAO,MAAK,EACjD,EACAT,EAACiI,GAAA,EAAQ,GACX,CAEJ,CAEA,SAASA,IAAU,CACjB,IAAM5F,EAAMC,EAAO,EACnB,OAAAgC,EAAS,CAACC,EAAQC,IAAQ,CACpBA,EAAI,QAAQnC,EAAI,KAAK,CAC3B,CAAC,EACM,IACT,CAEA,SAASL,GAAS,CAAE,KAAAkG,EAAM,MAAAC,CAAM,EAAoC,CAClE,OACEnI,EAAC8G,EAAA,CAAI,WAAW,SAAS,eAAe,SAAS,MAAM,OAAO,OAAO,OACnE,SAAA9G,EAACiH,EAAA,CAAK,MAAOkB,EAAQ,SAAAD,EAAK,EAC5B,CAEJ,CAEA,SAAS9B,GAAYQ,EAMlB,CACD,GAAM,CAAE,QAAA1B,CAAQ,EAAI0B,EACd5B,EAAUE,EAAQ,WAAa,IAC/BD,EAAMD,EAAU,EAAI,KAAK,MAAOE,EAAQ,UAAYF,EAAW,EAAE,EAAI,GAAK,EAC1EoD,EAAa,OAAO,KAAKlD,EAAQ,aAAa,EAAE,OAChDmD,EAAMnD,EAAQ,YAAc,EAAI,EAAI,KAAK,IAAI,GAAIA,EAAQ,UAAYkD,GAAclD,EAAQ,SAAS,EACpGY,EAAS,KAAK,MAAMuC,EAAM,GAAI,EAAI,GAElC5H,EAAIC,EAAW,EACf2G,EAAW5G,EAAE,SAAS,MAAMmG,EAAM,IAAI,EACtCW,EAAOrB,EAAaU,EAAM,SAAU,EAAE,EACtCmB,EACJnB,EAAM,OAAS,SACX,GAAGW,CAAI,WAAQ9G,EAAE,SAAS,WAAW,GACrC,GAAG8G,CAAI,WAAQ9G,EAAE,SAAS,aAAamG,EAAM,aAAe,EAAGA,EAAM,aAAa,CAAC,WAAQS,CAAQ,GAQnGiB,EAAS,SALb1B,EAAM,OAAS,OACXnG,EAAE,SAAS,QAAQ,UACnBmG,EAAM,OAAS,UAAYA,EAAM,aAAe,GAAKA,EAAM,cACzDnG,EAAE,SAAS,QAAQ,SACnBA,EAAE,SAAS,QAAQ,WACM,aAAUA,EAAE,SAAS,QAAQ,cAAc,eAAYA,EAAE,SAAS,QAAQ,QAAQ,GAEnH,OACER,EAAC6G,EAAA,CAAI,cAAc,SAAS,WAAW,SAAS,eAAe,SAAS,SAAU,EAAG,MAAM,OAAO,OAAO,OACvG,UAAA9G,EAACiH,EAAA,CAAK,KAAI,GAAC,MAAOhF,EAAQ,QACvB,SAAAxB,EAAE,SAAS,gBACd,EACAT,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAA8F,EAAS,EACxC,EAEA9H,EAAC6G,EAAA,CAAI,UAAW,EAAG,cAAc,MAAM,eAAe,SACpD,UAAA9G,EAACuI,EAAA,CAAS,MAAO9H,EAAE,SAAS,UAAU,MAAO,MAAO,OAAOyE,EAAQ,SAAS,EAAG,MAAOjD,EAAQ,KAAM,EACpGjC,EAACuI,EAAA,CACC,MAAO9H,EAAE,SAAS,UAAU,OAC5B,MAAO,OAAOyE,EAAQ,MAAM,EAC5B,MAAOA,EAAQ,OAAS,EAAIjD,EAAQ,MAAQA,EAAQ,MACtD,EACAjC,EAACuI,EAAA,CAAS,MAAO9H,EAAE,SAAS,UAAU,IAAK,MAAO,OAAOwE,CAAG,EAAG,MAAOhD,EAAQ,OAAQ,EACtFjC,EAACuI,EAAA,CAAS,MAAO9H,EAAE,SAAS,UAAU,SAAU,MAAO,GAAGqF,CAAM,IAAK,MAAO7D,EAAQ,OAAQ,GAC9F,EAEAjC,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAxB,EAAE,SAAS,UAAU,QAAQ+F,GAAQtB,EAAQ,UAAU,CAAC,EAAE,EACzF,EAEAlF,EAAC8G,EAAA,CAAI,SAAU,EAAG,EAElB9G,EAAC8G,EAAA,CAAI,UAAW,EACd,SAAA9G,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAqG,EAAO,EACtC,GACF,CAEJ,CAEA,SAASC,EAAS,CAAE,MAAAC,EAAO,MAAAC,EAAO,MAAAN,CAAM,EAAoD,CAC1F,OACElI,EAAC6G,EAAA,CAAI,cAAc,SAAS,WAAW,SAAS,QAAS,EACvD,UAAA9G,EAACiH,EAAA,CAAK,KAAI,GAAC,MAAOkB,EAAQ,SAAAM,EAAM,EAChCzI,EAACiH,EAAA,CAAK,MAAOhF,EAAQ,MAAQ,SAAAuG,EAAM,GACrC,CAEJ","names":["useState","useEffect","useRef","Box","Text","useApp","useInput","shuffle","arr","rng","out","i","j","tmp","mulberry32","seed","t","chunkChapters","words","chapterSize","chunks","i","buildPlaylist","chapter","mode","seed","rng","mulberry32","shuffle","initialState","target","reduce","state","ev","candidate","targetUpToCandidate","startSession","playlist","now","initialState","feedSession","session","ev","state","effect","reduce","finished","nextIndex","results","skipSession","result","sessionSummary","errors","a","r","durationMs","perWordErrors","useEffect","useReducer","useRef","useState","useInput","useApp","reducer","state","action","startSession","skipSession","r","feedSession","session","lastEffect","classifyInputBatch","input","chars","c","cleaned","cp","useWordLoop","playlist","onComplete","onTab","onEscape","onSkip","onImeBlock","onValidInput","enabled","dispatch","useReducer","completedRef","useRef","tick","setTick","useState","exit","useApp","useInput","key","kind","useEffect","id","t","useEffect","useRef","useAudio","opts","initedRef","useRef","useEffect","initAudio","playKeystroke","playCorrect","playWrong","word","playPronunciation","prefetchPronunciation","useCallback","useSessionPersistence","meta","useCallback","summary","rec","appendSession","addChapter","dirty","n","book","loadMistakes","word","bump","saveMistakes","Box","Text","useStdout","Fragment","jsx","jsxs","RIGHT_WIDTH","fmtTime","ms","total","m","s","useLeftWidth","stdout","useStdout","cols","Row","left","right","leftWidth","Box","StealthTyping","props","t","useStrings","target","typed","wordCell","ch","i","isTyped","display","color","PALETTE","Text","translationCell","info","accFmt","showAudio","right1","right2","right3","StealthPaused","StealthSummary","line","jsx","jsxs","PracticeScreen","params","dictId","chapterIndex","mode","cfg","useAppState","t","useStrings","phase","setPhase","useState","loaded","setLoaded","errorMsg","setErrorMsg","useEffect","cancelled","words","ensureDictionary","book","loadMistakes","reviewWords","w","chapters","chunkChapters","idx","playlist","buildPlaylist","err","Centered","PALETTE","ErrorView","PracticeRunner","stealth","nav","useNav","exit","useApp","goBack","persist","useSessionPersistence","dictName","useDictName","audio","useAudio","audioStatus","useAudioStatus","audioWarn","finishedRef","useRef","lastEffectRef","lastIndexRef","infoVisible","setInfoVisible","infoShownAt","setInfoShownAt","imeBlocked","setImeBlocked","id","session","lastEffect","tick","useWordLoop","s","sessionSummary","cur","next","useInput","_input","key","input","nextIdx","completed","errors","a","r","elapsedMs","minutes","wpm","summary","StealthPaused","sMinutes","sWpm","sErrWords","sAcc","sAccPct","StealthSummary","currentWord","inputState","errWords","accFrac","accPct","chapterLabel","StealthTyping","pickPhonetic","truncateName","PausedView","SummaryView","TypingLayout","word","accent","fmtTime","ms","total","m","props","progressFrac","Box","StatusBar","BigWord","Text","tr","i","ProgressBar","modeName","accentName","name","left","right","frac","cols","width","filled","empty","subtitle","msg","BackKey","text","color","errorWords","acc","footer","StatCard","label","value"]}
@@ -1,2 +1,2 @@
1
- import{b as O,c as V,d as Y,e as j,g as F}from"./chunk-MPE25TTQ.js";import{a as N,d as $}from"./chunk-UPA4JFCH.js";import{b as B,d as k,e as y}from"./chunk-IUFBN3RD.js";import{b as R,d as C,f as o}from"./chunk-SSDQJ6MT.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useEffect as G,useState as D}from"react";import{Box as i,Text as r,useInput as U,useStdout as q}from"ink";import{jsx as e,jsxs as c}from"react/jsx-runtime";var W=[7,14,30,90];function it(){let s=R(),t=C(),[n,u]=D(null),[x,g]=D(null),[d,b]=D(1);if(G(()=>{(async()=>{let[l,a]=await Promise.all([O(),N()]);u(l),g(a)})()},[]),U((l,a)=>{if(a.escape){s.back();return}a.rightArrow&&b(M=>(M+1)%W.length),a.leftArrow&&b(M=>(M-1+W.length)%W.length)}),!n||!x)return e(i,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:e(r,{color:o.muted,children:t.stats.loading})});if(n.length===0)return c(i,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[e(r,{color:o.muted,children:t.stats.none}),e(i,{marginTop:1,children:e(r,{color:o.muted,children:t.stats.nonePractice})}),e(i,{marginTop:2,children:c(r,{color:o.muted,children:["Esc ",t.common.back]})})]});let m=W[d],S=j(n),f=n.reduce((l,a)=>l+a.wordCount,0),w=n.reduce((l,a)=>l+a.errors,0),T=n.reduce((l,a)=>l+a.durationMs,0),v=n.reduce((l,a)=>l+(a.wordCount-Object.keys(a.perWordErrors).length),0),H=T>0?Math.round(f/(T/6e4)*10)/10:0,X=f===0?1:v/f,_=n.slice(-5).reverse(),I=$(x,8),p=F(n,m),A=p.peakWpmLifetime>0?Math.min(1,p.avgWpm/p.peakWpmLifetime):0,L=Math.min(1,p.avgAccPct/100),P=m>0?Math.min(1,p.sessionsInWindow/m):0;return c(i,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[e(r,{bold:!0,color:o.accent,children:t.stats.title}),c(i,{marginTop:1,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.lifetime}),c(i,{marginTop:1,children:[e(h,{label:t.stats.sessions,value:String(n.length)}),e(h,{label:t.stats.words,value:String(f)}),e(h,{label:t.stats.errors,value:String(w)}),e(h,{label:t.stats.wpm,value:String(H),accent:!0}),e(h,{label:t.stats.accuracy,value:`${Math.round(X*1e3)/10}%`,accent:!0}),e(h,{label:t.stats.streak,value:`${S}d`,accent:!0})]})]}),c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.last(m)}),c(i,{marginTop:1,flexDirection:"column",borderStyle:"round",borderColor:o.muted,paddingX:2,paddingY:0,children:[e(E,{label:t.stats.bars.speed,frac:A,valueText:`${p.avgWpm} wpm`,pct:Math.round(A*100)}),e(E,{label:t.stats.bars.accuracy,frac:L,valueText:`${p.avgAccPct}%`,pct:Math.round(L*100)}),e(E,{label:t.stats.bars.sessions,frac:P,valueText:`${p.sessionsInWindow} / ${m}`,pct:Math.round(P*100)})]})]}),c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.recent}),_.map((l,a)=>e(z,{session:l,units:t.stats.recentUnits},a))]}),I.length>0&&c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.topMistakes}),I.map(([l,a])=>e(J,{word:l,count:a.count,dictIds:a.dictIds,multiSuffix:t.stats.multiDictSuffix},l))]}),e(i,{flexGrow:1}),e(i,{marginTop:1,children:e(r,{color:o.muted,children:t.stats.footer})})]})}function z({session:s,units:t}){let n=B(s.dictId),u=y(n,14);return c(i,{children:[c(r,{color:o.muted,children:[" ",s.ts.replace("T"," ").slice(0,16)," "]}),e(r,{color:o.text,children:u.padEnd(14)}),c(r,{color:o.muted,children:[" ","ch",String(s.chapter+1).padStart(3)," ",s.mode.padEnd(9)," ",String(s.wordCount).padStart(3),t.words," ",s.errors,t.errors," ",V(s),t.wpm," ",Math.round(Y(s)*1e3)/10,"%"]})]})}function J({word:s,count:t,dictIds:n,multiSuffix:u}){let x=n[0]??"",g=B(x),d=n.length>1?u(n.length-1):"";return c(i,{children:[c(r,{color:o.error,children:[" ",String(t).padStart(3)," "]}),e(r,{color:o.text,children:s.padEnd(20)}),c(r,{color:o.muted,children:[y(g,20),d]})]})}function h({label:s,value:t,accent:n=!1}){return c(i,{marginRight:3,children:[c(r,{color:o.muted,children:[s," "]}),e(r,{bold:!0,color:n?o.accent:o.text,children:t})]})}function K(s){return{barWidth:Math.max(0,Math.min(60,s-11-24)),labelWidth:9,valueWidth:8,pctWidth:5,marginLeft:2}}function E({label:s,frac:t,valueText:n,pct:u}){let{stdout:x}=q(),g=x?.columns??80,d=K(g),b=Math.max(0,Math.min(1,t)),m=Math.round(d.barWidth*b),S=d.barWidth-m,f=k(s,d.labelWidth),w=k(n,d.valueWidth),T=String(u).padStart(d.pctWidth-1," ")+"%",v=" ".repeat(d.marginLeft);return c(i,{children:[e(r,{color:o.muted,children:f}),e(r,{color:o.accent,children:"\u2501".repeat(m)}),e(r,{color:o.muted,children:"\u2500".repeat(S)}),c(r,{color:o.text,children:[v,w]}),e(r,{color:o.muted,children:T})]})}export{it as StatsViewer,K as calcMetricBarLayout};
2
- //# sourceMappingURL=StatsViewer-PDXZ7PJR.js.map
1
+ import{b as O,c as V,d as Y,e as j,g as F}from"./chunk-MPE25TTQ.js";import{a as N,d as $}from"./chunk-UPA4JFCH.js";import{b as B,d as k,e as y}from"./chunk-IUFBN3RD.js";import{b as R,d as C,f as o}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useEffect as G,useState as D}from"react";import{Box as i,Text as r,useInput as U,useStdout as q}from"ink";import{jsx as e,jsxs as c}from"react/jsx-runtime";var W=[7,14,30,90];function it(){let s=R(),t=C(),[n,u]=D(null),[x,g]=D(null),[d,b]=D(1);if(G(()=>{(async()=>{let[l,a]=await Promise.all([O(),N()]);u(l),g(a)})()},[]),U((l,a)=>{if(a.escape){s.back();return}a.rightArrow&&b(M=>(M+1)%W.length),a.leftArrow&&b(M=>(M-1+W.length)%W.length)}),!n||!x)return e(i,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:e(r,{color:o.muted,children:t.stats.loading})});if(n.length===0)return c(i,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[e(r,{color:o.muted,children:t.stats.none}),e(i,{marginTop:1,children:e(r,{color:o.muted,children:t.stats.nonePractice})}),e(i,{marginTop:2,children:c(r,{color:o.muted,children:["Esc ",t.common.back]})})]});let m=W[d],S=j(n),f=n.reduce((l,a)=>l+a.wordCount,0),w=n.reduce((l,a)=>l+a.errors,0),T=n.reduce((l,a)=>l+a.durationMs,0),v=n.reduce((l,a)=>l+(a.wordCount-Object.keys(a.perWordErrors).length),0),H=T>0?Math.round(f/(T/6e4)*10)/10:0,X=f===0?1:v/f,_=n.slice(-5).reverse(),I=$(x,8),p=F(n,m),A=p.peakWpmLifetime>0?Math.min(1,p.avgWpm/p.peakWpmLifetime):0,L=Math.min(1,p.avgAccPct/100),P=m>0?Math.min(1,p.sessionsInWindow/m):0;return c(i,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[e(r,{bold:!0,color:o.accent,children:t.stats.title}),c(i,{marginTop:1,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.lifetime}),c(i,{marginTop:1,children:[e(h,{label:t.stats.sessions,value:String(n.length)}),e(h,{label:t.stats.words,value:String(f)}),e(h,{label:t.stats.errors,value:String(w)}),e(h,{label:t.stats.wpm,value:String(H),accent:!0}),e(h,{label:t.stats.accuracy,value:`${Math.round(X*1e3)/10}%`,accent:!0}),e(h,{label:t.stats.streak,value:`${S}d`,accent:!0})]})]}),c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.last(m)}),c(i,{marginTop:1,flexDirection:"column",borderStyle:"round",borderColor:o.muted,paddingX:2,paddingY:0,children:[e(E,{label:t.stats.bars.speed,frac:A,valueText:`${p.avgWpm} wpm`,pct:Math.round(A*100)}),e(E,{label:t.stats.bars.accuracy,frac:L,valueText:`${p.avgAccPct}%`,pct:Math.round(L*100)}),e(E,{label:t.stats.bars.sessions,frac:P,valueText:`${p.sessionsInWindow} / ${m}`,pct:Math.round(P*100)})]})]}),c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.recent}),_.map((l,a)=>e(z,{session:l,units:t.stats.recentUnits},a))]}),I.length>0&&c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.topMistakes}),I.map(([l,a])=>e(J,{word:l,count:a.count,dictIds:a.dictIds,multiSuffix:t.stats.multiDictSuffix},l))]}),e(i,{flexGrow:1}),e(i,{marginTop:1,children:e(r,{color:o.muted,children:t.stats.footer})})]})}function z({session:s,units:t}){let n=B(s.dictId),u=y(n,14);return c(i,{children:[c(r,{color:o.muted,children:[" ",s.ts.replace("T"," ").slice(0,16)," "]}),e(r,{color:o.text,children:u.padEnd(14)}),c(r,{color:o.muted,children:[" ","ch",String(s.chapter+1).padStart(3)," ",s.mode.padEnd(9)," ",String(s.wordCount).padStart(3),t.words," ",s.errors,t.errors," ",V(s),t.wpm," ",Math.round(Y(s)*1e3)/10,"%"]})]})}function J({word:s,count:t,dictIds:n,multiSuffix:u}){let x=n[0]??"",g=B(x),d=n.length>1?u(n.length-1):"";return c(i,{children:[c(r,{color:o.error,children:[" ",String(t).padStart(3)," "]}),e(r,{color:o.text,children:s.padEnd(20)}),c(r,{color:o.muted,children:[y(g,20),d]})]})}function h({label:s,value:t,accent:n=!1}){return c(i,{marginRight:3,children:[c(r,{color:o.muted,children:[s," "]}),e(r,{bold:!0,color:n?o.accent:o.text,children:t})]})}function K(s){return{barWidth:Math.max(0,Math.min(60,s-11-24)),labelWidth:9,valueWidth:8,pctWidth:5,marginLeft:2}}function E({label:s,frac:t,valueText:n,pct:u}){let{stdout:x}=q(),g=x?.columns??80,d=K(g),b=Math.max(0,Math.min(1,t)),m=Math.round(d.barWidth*b),S=d.barWidth-m,f=k(s,d.labelWidth),w=k(n,d.valueWidth),T=String(u).padStart(d.pctWidth-1," ")+"%",v=" ".repeat(d.marginLeft);return c(i,{children:[e(r,{color:o.muted,children:f}),e(r,{color:o.accent,children:"\u2501".repeat(m)}),e(r,{color:o.muted,children:"\u2500".repeat(S)}),c(r,{color:o.text,children:[v,w]}),e(r,{color:o.muted,children:T})]})}export{it as StatsViewer,K as calcMetricBarLayout};
2
+ //# sourceMappingURL=StatsViewer-3CUMIAV4.js.map
@@ -1,2 +1,2 @@
1
- import{c as b}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{a as M}from"./chunk-UPA4JFCH.js";import{b as B,e as h}from"./chunk-IUFBN3RD.js";import{b as L,d as g,f as t}from"./chunk-SSDQJ6MT.js";import"./chunk-6QICLHIY.js";import{a as I}from"./chunk-6KRVNT2S.js";import{useEffect as E,useState as u}from"react";import{Box as n,Text as r,useInput as S}from"ink";import{readdir as $}from"fs/promises";import{Fragment as G,jsx as o,jsxs as d}from"react/jsx-runtime";async function N(){try{return(await $(I.dictsDir)).filter(e=>e.endsWith(".json")&&!e.endsWith(".meta.json")).map(e=>e.replace(/\.json$/,""))}catch{return[]}}function O(){let i=L(),e=g(),[m,x]=u(""),[l,w]=u([]),[W,C]=u({}),[H,j]=u(!0),[k,f]=u(0);E(()=>{(async()=>{let a=await N(),c=[];for(let s of a){let y=await b(s);if(y)for(let A of y)c.push({dictId:s,word:A})}w(c),C(await M()),j(!1)})()},[]);let T=m.toLowerCase().trim(),p=T?l.filter(a=>a.word.name.toLowerCase().includes(T)).slice(0,50):[];if(S((a,c)=>{if(c.escape){i.back();return}if(c.upArrow){f(s=>Math.max(0,s-1));return}if(c.downArrow){f(s=>Math.min(p.length-1,s+1));return}if(c.backspace||c.delete){x(s=>s.slice(0,-1)),f(0);return}a&&!c.ctrl&&!c.meta&&a.trim().length>0&&(x(s=>s+a),f(0))}),H)return o(n,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(r,{color:t.muted,children:e.word.indexing})});if(l.length===0)return d(n,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(r,{color:t.muted,children:e.word.none}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.pullFirst})}),o(n,{marginTop:2,children:d(r,{color:t.muted,children:["[Esc] ",e.common.back]})})]});let D=p[k];return d(n,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[d(n,{children:[o(r,{bold:!0,color:t.accent,children:e.word.title}),o(n,{flexGrow:1}),o(r,{color:t.muted,children:e.word.countAcross(l.length)})]}),d(n,{marginTop:1,children:[o(r,{color:t.muted,children:"> "}),o(r,{color:t.text,children:m}),o(r,{color:t.accent,children:"_"})]}),d(n,{marginTop:1,flexGrow:1,children:[d(n,{flexDirection:"column",width:"40%",children:[p.map((a,c)=>o(q,{hit:a,active:c===k},`${a.dictId}-${a.word.name}-${c}`)),p.length===0&&T&&o(r,{color:t.muted,children:e.word.noMatches(m)})]}),o(n,{flexDirection:"column",width:"60%",paddingLeft:2,children:D&&o(v,{hit:D,book:W})})]}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.footer})})]})}function q({hit:i,active:e}){let m=B(i.dictId);return d(n,{children:[o(r,{color:e?t.accent:t.muted,children:e?"\u258C ":" "}),o(r,{bold:e,color:e?t.text:t.muted,children:i.word.name.padEnd(20)}),o(r,{color:t.muted,children:h(m,18)})]})}function v({hit:i,book:e}){let m=g(),x=B(i.dictId);return d(G,{children:[o(r,{bold:!0,color:t.text,children:i.word.name}),d(n,{marginTop:1,children:[i.word.usphone&&d(r,{dimColor:!0,color:t.muted,children:["US ",i.word.usphone," "]}),i.word.ukphone&&d(r,{dimColor:!0,color:t.muted,children:["UK ",i.word.ukphone]})]}),o(n,{marginTop:1,flexDirection:"column",children:(i.word.trans??[]).map((l,w)=>d(r,{color:t.primary,children:["\xB7 ",l]},w))}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:m.word.inDict(h(x,22))})}),e[i.word.name]&&o(n,{marginTop:1,children:o(r,{color:t.error,children:m.word.mistakes(e[i.word.name].count,e[i.word.name].lastSeen.slice(0,10))})})]})}export{O as WordLookup};
2
- //# sourceMappingURL=WordLookup-EAXT7PGW.js.map
1
+ import{c as b}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{a as M}from"./chunk-UPA4JFCH.js";import{b as B,e as h}from"./chunk-IUFBN3RD.js";import{b as L,d as g,f as t}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import{a as I}from"./chunk-6KRVNT2S.js";import{useEffect as E,useState as u}from"react";import{Box as n,Text as r,useInput as S}from"ink";import{readdir as $}from"fs/promises";import{Fragment as G,jsx as o,jsxs as d}from"react/jsx-runtime";async function N(){try{return(await $(I.dictsDir)).filter(e=>e.endsWith(".json")&&!e.endsWith(".meta.json")).map(e=>e.replace(/\.json$/,""))}catch{return[]}}function O(){let i=L(),e=g(),[m,x]=u(""),[l,w]=u([]),[W,C]=u({}),[H,j]=u(!0),[k,f]=u(0);E(()=>{(async()=>{let a=await N(),c=[];for(let s of a){let y=await b(s);if(y)for(let A of y)c.push({dictId:s,word:A})}w(c),C(await M()),j(!1)})()},[]);let T=m.toLowerCase().trim(),p=T?l.filter(a=>a.word.name.toLowerCase().includes(T)).slice(0,50):[];if(S((a,c)=>{if(c.escape){i.back();return}if(c.upArrow){f(s=>Math.max(0,s-1));return}if(c.downArrow){f(s=>Math.min(p.length-1,s+1));return}if(c.backspace||c.delete){x(s=>s.slice(0,-1)),f(0);return}a&&!c.ctrl&&!c.meta&&a.trim().length>0&&(x(s=>s+a),f(0))}),H)return o(n,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(r,{color:t.muted,children:e.word.indexing})});if(l.length===0)return d(n,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(r,{color:t.muted,children:e.word.none}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.pullFirst})}),o(n,{marginTop:2,children:d(r,{color:t.muted,children:["[Esc] ",e.common.back]})})]});let D=p[k];return d(n,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[d(n,{children:[o(r,{bold:!0,color:t.accent,children:e.word.title}),o(n,{flexGrow:1}),o(r,{color:t.muted,children:e.word.countAcross(l.length)})]}),d(n,{marginTop:1,children:[o(r,{color:t.muted,children:"> "}),o(r,{color:t.text,children:m}),o(r,{color:t.accent,children:"_"})]}),d(n,{marginTop:1,flexGrow:1,children:[d(n,{flexDirection:"column",width:"40%",children:[p.map((a,c)=>o(q,{hit:a,active:c===k},`${a.dictId}-${a.word.name}-${c}`)),p.length===0&&T&&o(r,{color:t.muted,children:e.word.noMatches(m)})]}),o(n,{flexDirection:"column",width:"60%",paddingLeft:2,children:D&&o(v,{hit:D,book:W})})]}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.footer})})]})}function q({hit:i,active:e}){let m=B(i.dictId);return d(n,{children:[o(r,{color:e?t.accent:t.muted,children:e?"\u258C ":" "}),o(r,{bold:e,color:e?t.text:t.muted,children:i.word.name.padEnd(20)}),o(r,{color:t.muted,children:h(m,18)})]})}function v({hit:i,book:e}){let m=g(),x=B(i.dictId);return d(G,{children:[o(r,{bold:!0,color:t.text,children:i.word.name}),d(n,{marginTop:1,children:[i.word.usphone&&d(r,{dimColor:!0,color:t.muted,children:["US ",i.word.usphone," "]}),i.word.ukphone&&d(r,{dimColor:!0,color:t.muted,children:["UK ",i.word.ukphone]})]}),o(n,{marginTop:1,flexDirection:"column",children:(i.word.trans??[]).map((l,w)=>d(r,{color:t.primary,children:["\xB7 ",l]},w))}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:m.word.inDict(h(x,22))})}),e[i.word.name]&&o(n,{marginTop:1,children:o(r,{color:t.error,children:m.word.mistakes(e[i.word.name].count,e[i.word.name].lastSeen.slice(0,10))})})]})}export{O as WordLookup};
2
+ //# sourceMappingURL=WordLookup-YIU6LP4H.js.map
@@ -0,0 +1,2 @@
1
+ import{a as c,g as p}from"./chunk-KBRGNL2D.js";import{createContext as A,useContext as h,useEffect as b,useState as w}from"react";import{jsx as y}from"react/jsx-runtime";var l=A({warning:null,ready:!1});function g({disabled:r,children:e}){let[n,u]=w({warning:null,ready:!1});return b(()=>{let s=!1;return c(r).then(()=>{s||u({warning:p(),ready:!0})}).catch(()=>{s||u({warning:null,ready:!0})}),()=>{s=!0}},[r]),y(l.Provider,{value:n,children:e})}function x(){return h(l)}var t={startedAt:null,chapters:[]};function E(r=Date.now()){t.startedAt===null&&(t.startedAt=r)}function W(r){t.startedAt===null&&(t.startedAt=Date.now()),t.chapters.push(r)}function D(r=Date.now()){let e=t.chapters,n=e.reduce((o,a)=>o+a.wordCount,0),u=e.reduce((o,a)=>o+a.errors,0),s=e.reduce((o,a)=>o+a.durationMs,0),i=s/6e4,m=i>0?Math.round(n/i*10)/10:0,d=new Set;for(let o of e)for(let a of Object.keys(o.perWordErrors))d.add(a);let f=n===0?1:Math.max(0,(n-d.size)/n);return{startedAt:t.startedAt,totalDurationMs:t.startedAt===null?0:r-t.startedAt,chaptersCompleted:e.length,wordCount:n,errors:u,wpm:m,accuracy:f,newMistakeWords:d.size,practiceMs:s}}export{g as a,x as b,E as c,W as d,D as e};
2
+ //# sourceMappingURL=chunk-7RMRK5MO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/audio-context.tsx","../src/infra/session-tracker.ts"],"sourcesContent":["import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { initAudio, audioWarning } from '../infra/audio.js';\n\ntype AudioStatus = { warning: string | null; ready: boolean };\n\nconst AudioStatusContext = createContext<AudioStatus>({ warning: null, ready: false });\n\nexport function AudioStatusProvider({\n disabled,\n children,\n}: {\n disabled: boolean;\n children: ReactNode;\n}) {\n const [status, setStatus] = useState<AudioStatus>({ warning: null, ready: false });\n\n useEffect(() => {\n let cancelled = false;\n initAudio(disabled)\n .then(() => {\n if (cancelled) return;\n setStatus({ warning: audioWarning(), ready: true });\n })\n .catch(() => {\n if (cancelled) return;\n setStatus({ warning: null, ready: true });\n });\n return () => {\n cancelled = true;\n };\n }, [disabled]);\n\n return <AudioStatusContext.Provider value={status}>{children}</AudioStatusContext.Provider>;\n}\n\nexport function useAudioStatus(): AudioStatus {\n return useContext(AudioStatusContext);\n}\n","type ChapterEntry = {\n dictId: string;\n chapterIndex: number;\n mode: string;\n wordCount: number;\n errors: number;\n durationMs: number;\n perWordErrors: Record<string, number>;\n};\n\ntype State = {\n startedAt: number | null;\n chapters: ChapterEntry[];\n};\n\nconst state: State = {\n startedAt: null,\n chapters: [],\n};\n\nexport function start(now = Date.now()): void {\n if (state.startedAt === null) state.startedAt = now;\n}\n\nexport function addChapter(entry: ChapterEntry): void {\n if (state.startedAt === null) state.startedAt = Date.now();\n state.chapters.push(entry);\n}\n\nexport type SessionReport = {\n startedAt: number | null;\n totalDurationMs: number;\n chaptersCompleted: number;\n wordCount: number;\n errors: number;\n wpm: number;\n accuracy: number;\n newMistakeWords: number;\n practiceMs: number;\n};\n\nexport function report(now = Date.now()): SessionReport {\n const chapters = state.chapters;\n const wordCount = chapters.reduce((a, c) => a + c.wordCount, 0);\n const errors = chapters.reduce((a, c) => a + c.errors, 0);\n const practiceMs = chapters.reduce((a, c) => a + c.durationMs, 0);\n const minutes = practiceMs / 60000;\n const wpm = minutes > 0 ? Math.round((wordCount / minutes) * 10) / 10 : 0;\n const errorWordSet = new Set<string>();\n for (const c of chapters) {\n for (const w of Object.keys(c.perWordErrors)) errorWordSet.add(w);\n }\n const accuracy = wordCount === 0 ? 1 : Math.max(0, (wordCount - errorWordSet.size) / wordCount);\n return {\n startedAt: state.startedAt,\n totalDurationMs: state.startedAt === null ? 0 : now - state.startedAt,\n chaptersCompleted: chapters.length,\n wordCount,\n errors,\n wpm,\n accuracy,\n newMistakeWords: errorWordSet.size,\n practiceMs,\n };\n}\n\nexport function reset(): void {\n state.startedAt = null;\n state.chapters = [];\n}\n"],"mappings":"+CAAA,OAAS,iBAAAA,EAAe,cAAAC,EAAY,aAAAC,EAAW,YAAAC,MAAgC,QAgCtE,cAAAC,MAAA,oBA3BT,IAAMC,EAAqBC,EAA2B,CAAE,QAAS,KAAM,MAAO,EAAM,CAAC,EAE9E,SAASC,EAAoB,CAClC,SAAAC,EACA,SAAAC,CACF,EAGG,CACD,GAAM,CAACC,EAAQC,CAAS,EAAIC,EAAsB,CAAE,QAAS,KAAM,MAAO,EAAM,CAAC,EAEjF,OAAAC,EAAU,IAAM,CACd,IAAIC,EAAY,GAChB,OAAAC,EAAUP,CAAQ,EACf,KAAK,IAAM,CACNM,GACJH,EAAU,CAAE,QAASK,EAAa,EAAG,MAAO,EAAK,CAAC,CACpD,CAAC,EACA,MAAM,IAAM,CACPF,GACJH,EAAU,CAAE,QAAS,KAAM,MAAO,EAAK,CAAC,CAC1C,CAAC,EACI,IAAM,CACXG,EAAY,EACd,CACF,EAAG,CAACN,CAAQ,CAAC,EAENJ,EAACC,EAAmB,SAAnB,CAA4B,MAAOK,EAAS,SAAAD,EAAS,CAC/D,CAEO,SAASQ,GAA8B,CAC5C,OAAOC,EAAWb,CAAkB,CACtC,CCtBA,IAAMc,EAAe,CACnB,UAAW,KACX,SAAU,CAAC,CACb,EAEO,SAASC,EAAMC,EAAM,KAAK,IAAI,EAAS,CACxCF,EAAM,YAAc,OAAMA,EAAM,UAAYE,EAClD,CAEO,SAASC,EAAWC,EAA2B,CAChDJ,EAAM,YAAc,OAAMA,EAAM,UAAY,KAAK,IAAI,GACzDA,EAAM,SAAS,KAAKI,CAAK,CAC3B,CAcO,SAASC,EAAOH,EAAM,KAAK,IAAI,EAAkB,CACtD,IAAMI,EAAWN,EAAM,SACjBO,EAAYD,EAAS,OAAO,CAACE,EAAGC,IAAMD,EAAIC,EAAE,UAAW,CAAC,EACxDC,EAASJ,EAAS,OAAO,CAACE,EAAGC,IAAMD,EAAIC,EAAE,OAAQ,CAAC,EAClDE,EAAaL,EAAS,OAAO,CAACE,EAAGC,IAAMD,EAAIC,EAAE,WAAY,CAAC,EAC1DG,EAAUD,EAAa,IACvBE,EAAMD,EAAU,EAAI,KAAK,MAAOL,EAAYK,EAAW,EAAE,EAAI,GAAK,EAClEE,EAAe,IAAI,IACzB,QAAWL,KAAKH,EACd,QAAWS,KAAK,OAAO,KAAKN,EAAE,aAAa,EAAGK,EAAa,IAAIC,CAAC,EAElE,IAAMC,EAAWT,IAAc,EAAI,EAAI,KAAK,IAAI,GAAIA,EAAYO,EAAa,MAAQP,CAAS,EAC9F,MAAO,CACL,UAAWP,EAAM,UACjB,gBAAiBA,EAAM,YAAc,KAAO,EAAIE,EAAMF,EAAM,UAC5D,kBAAmBM,EAAS,OAC5B,UAAAC,EACA,OAAAG,EACA,IAAAG,EACA,SAAAG,EACA,gBAAiBF,EAAa,KAC9B,WAAAH,CACF,CACF","names":["createContext","useContext","useEffect","useState","jsx","AudioStatusContext","createContext","AudioStatusProvider","disabled","children","status","setStatus","useState","useEffect","cancelled","initAudio","audioWarning","useAudioStatus","useContext","state","start","now","addChapter","entry","report","chapters","wordCount","a","c","errors","practiceMs","minutes","wpm","errorWordSet","w","accuracy"]}
@@ -0,0 +1,3 @@
1
+ import{a as f,b as y,c as p,d as g}from"./chunk-6KRVNT2S.js";import{spawn as w}from"child_process";import{appendFile as x,mkdir as v,rename as Q,writeFile as A}from"fs/promises";import{join as c,dirname as h}from"path";function S(e){return e.replace(/'/g,"''")}function b(e,n){let t=S(e);return n==="wav"?["-NoProfile","-Command",`(New-Object System.Media.SoundPlayer '${t}').PlaySync();`]:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`Add-Type -AssemblyName presentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([Uri]::new('${t}')); $p.Play(); Start-Sleep -Milliseconds 3000`]}var T=[{kind:"afplay",platform:"unix",cmd:"afplay",args:e=>[e],supports:"both"},{kind:"ffplay",platform:"unix",cmd:"ffplay",args:e=>["-nodisp","-autoexit","-loglevel","quiet",e],supports:"both"},{kind:"mpg123",platform:"unix",cmd:"mpg123",args:e=>["-q",e],supports:"mp3"},{kind:"paplay",platform:"unix",cmd:"paplay",args:e=>[e],supports:"wav"},{kind:"aplay",platform:"unix",cmd:"aplay",args:e=>["-q",e],supports:"wav"},{kind:"powershell",platform:"win",cmd:"powershell",args:b,supports:"both"},{kind:"pwsh",platform:"win",cmd:"pwsh",args:b,supports:"both"}],$="https://dict.youdao.com/dictvoice?audio=",r=null,d=null;async function C(e){return new Promise(n=>{let t=w("where.exe",[e],{stdio:"ignore",windowsHide:!0});t.on("error",()=>n(!1)),t.on("exit",a=>n(a===0)),setTimeout(()=>{t.kill(),n(!1)},150)})}async function q(e){return new Promise(n=>{let t=w(e,["--version"],{stdio:"ignore"});t.on("error",()=>n(!1)),t.on("exit",()=>n(!0)),setTimeout(()=>{t.kill(),n(!1)},150)})}async function D(e){return process.platform==="win32"?C(e):q(e)}async function R(){let e=process.platform==="win32"?"win":"unix",n=T.filter(o=>o.platform===e),t=await Promise.all(n.map(async o=>[o,await D(o.cmd)])),a=null,i=null;for(let[o,u]of t)if(u&&(!a&&(o.supports==="wav"||o.supports==="both")&&(a=o),!i&&(o.supports==="mp3"||o.supports==="both")&&(i=o),a&&i))break;return{wav:a,mp3:i}}async function N(){return(await import("p-queue")).default}async function _(e){if(r)return r;let n=await N();if(e)return r={disabled:!0,wavPlayer:null,mp3Player:null,warning:null,keyQueue:new n({concurrency:1}),feedbackQueue:new n({concurrency:1}),pronQueue:new n({concurrency:1})},r;let{wav:t,mp3:a}=await R(),i=null;return!t&&!a?i=process.platform==="win32"?"No audio player found on PATH (looked for pwsh/powershell). Install PowerShell or add ffplay to PATH. Sounds disabled.":"No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay). Sounds disabled.":a||(i="No MP3 player found; word pronunciations will be skipped."),r={disabled:!t&&!a,wavPlayer:t,mp3Player:a,warning:i,keyQueue:new n({concurrency:2}),feedbackQueue:new n({concurrency:1}),pronQueue:new n({concurrency:1})},r}function I(){let e=process.env.QWERTY_AUDIO_DEBUG;return!!e&&e!=="0"&&e.toLowerCase()!=="false"}function E(){return c(f.root,"cache","audio-debug.log")}async function B(e){try{let n=E();await v(h(n),{recursive:!0}),await x(n,`${e}
2
+ `)}catch{}}function F(e,n,t){let a=I();try{let i=e.args(n,t),o=w(e.cmd,i,{detached:!0,stdio:a?["ignore","pipe","pipe"]:"ignore",windowsHide:!0});if(o.on("error",()=>{}),a){let u=new Date().toISOString(),l=[];o.stderr?.on("data",s=>l.push(s)),o.stdout?.resume(),o.on("exit",s=>{let P=Buffer.concat(l).toString("utf8").trim();B(`[${u}] ${e.cmd} ${i.join(" ")} | exit=${s??"null"}${P?` | stderr=${P.replace(/\n/g," ")}`:""}`)})}o.unref()}catch{}}function m(e,n){if(!r||r.disabled)return;let t=n==="wav"?r.wavPlayer:r.mp3Player;t&&F(t,e,n)}function j(){!r||r.disabled||r.keyQueue.size>=2||r.keyQueue.add(async()=>{m(c(p(),"sounds","key-default.wav"),"wav"),await new Promise(e=>setTimeout(e,30))})}function L(){!r||r.disabled||r.feedbackQueue.add(async()=>{m(c(p(),"sounds","correct.wav"),"wav"),await new Promise(e=>setTimeout(e,50))})}function z(){!r||r.disabled||r.feedbackQueue.add(async()=>{m(c(p(),"sounds","wrong.wav"),"wav"),await new Promise(e=>setTimeout(e,50))})}async function O(){return d||(d=(await import("undici")).request,d)}async function k(e,n){let t=f.audioCache(e,n);if(await g(t))return t;await v(h(t),{recursive:!0});let a=n==="us"?2:1,i=`${$}${encodeURIComponent(e)}&type=${a}`;try{let u=await(await O())(i,{headersTimeout:8e3,bodyTimeout:2e4});if(u.statusCode>=400)return null;let l=Buffer.from(await u.body.arrayBuffer());if(l.length<1024)return null;let s=`${t}.tmp`;return await A(s,l),await Q(s,t),t}catch{return null}}async function G(e,n){!r||r.disabled||!r.mp3Player||(await y(),await r.pronQueue.add(async()=>{let t=await k(e,n);t&&m(t,"mp3")}))}async function Y(e,n){!r||r.disabled||!r.mp3Player||(await y(),r.pronQueue.add(async()=>{await k(e,n)}))}function J(){return r?.warning??null}function V(){return r?.disabled??!0}function X(){let e=r?.wavPlayer,n=r?.mp3Player;return{wav:e?{kind:e.kind,cmd:e.cmd}:null,mp3:n?{kind:n.kind,cmd:n.cmd}:null}}export{_ as a,j as b,L as c,z as d,G as e,Y as f,J as g,V as h,X as i};
3
+ //# sourceMappingURL=chunk-KBRGNL2D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/infra/audio.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { appendFile, mkdir, rename, writeFile } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { paths, packageAssetsDir, ensureDirs } from './paths.js';\nimport { exists } from './fs-store.js';\n\n// undici and p-queue are dynamically imported at first use to keep them out\n// of the boot module graph — saves ~50ms at startup for the menu/--version\n// paths that don't touch audio at all.\ntype RequestFn = typeof import('undici').request;\ntype PQueueCtor = typeof import('p-queue').default;\n\ntype PlayerKind = 'afplay' | 'paplay' | 'aplay' | 'mpg123' | 'ffplay' | 'powershell' | 'pwsh';\n\ntype Player = {\n kind: PlayerKind;\n cmd: string;\n args: (file: string, fileKind: 'wav' | 'mp3') => string[];\n supports: 'wav' | 'mp3' | 'both';\n platform: 'unix' | 'win';\n};\n\n// Escape single quotes inside PowerShell single-quoted string literal: ' → ''.\n// This is the only escaping required because single-quoted PowerShell strings\n// don't interpolate $(...) or backticks.\nfunction escapePwshPath(file: string): string {\n return file.replace(/'/g, \"''\");\n}\n\n// Build PowerShell args. WAV uses SoundPlayer.PlaySync() which blocks the\n// child until audio finishes (clean, fast exit). MP3 uses MediaPlayer with a\n// 3s Start-Sleep — MediaPlayer.Play() is non-blocking, so we keep the process\n// alive long enough for typical pronunciations to finish.\nfunction buildPwshArgs(file: string, fileKind: 'wav' | 'mp3'): string[] {\n const escaped = escapePwshPath(file);\n if (fileKind === 'wav') {\n return [\n '-NoProfile',\n '-Command',\n `(New-Object System.Media.SoundPlayer '${escaped}').PlaySync();`,\n ];\n }\n return [\n '-NoProfile',\n '-ExecutionPolicy', 'Bypass',\n '-Command',\n `Add-Type -AssemblyName presentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([Uri]::new('${escaped}')); $p.Play(); Start-Sleep -Milliseconds 3000`,\n ];\n}\n\nconst CANDIDATES: Player[] = [\n { kind: 'afplay', platform: 'unix', cmd: 'afplay', args: (f) => [f], supports: 'both' },\n { kind: 'ffplay', platform: 'unix', cmd: 'ffplay', args: (f) => ['-nodisp', '-autoexit', '-loglevel', 'quiet', f], supports: 'both' },\n { kind: 'mpg123', platform: 'unix', cmd: 'mpg123', args: (f) => ['-q', f], supports: 'mp3' },\n { kind: 'paplay', platform: 'unix', cmd: 'paplay', args: (f) => [f], supports: 'wav' },\n { kind: 'aplay', platform: 'unix', cmd: 'aplay', args: (f) => ['-q', f], supports: 'wav' },\n // Windows: powershell.exe (PowerShell 5.1) first — ships in every Windows\n // install and runs on .NET Framework, where System.Media.SoundPlayer and\n // PresentationCore (WPF) MediaPlayer are always reachable. pwsh (PowerShell\n // 7+) is the fallback: it only exists when the user installed it, and on a\n // bare .NET runtime SoundPlayer/MediaPlayer can't load because they live in\n // the Microsoft.WindowsDesktop.App shared framework. Preferring pwsh first\n // caused silent failure for users without the Desktop Runtime.\n { kind: 'powershell', platform: 'win', cmd: 'powershell', args: buildPwshArgs, supports: 'both' },\n { kind: 'pwsh', platform: 'win', cmd: 'pwsh', args: buildPwshArgs, supports: 'both' },\n];\n\nconst PRON_API = 'https://dict.youdao.com/dictvoice?audio=';\n\ntype AudioRuntime = {\n disabled: boolean;\n wavPlayer: Player | null;\n mp3Player: Player | null;\n warning: string | null;\n keyQueue: InstanceType<PQueueCtor>;\n feedbackQueue: InstanceType<PQueueCtor>;\n pronQueue: InstanceType<PQueueCtor>;\n};\n\nlet runtime: AudioRuntime | null = null;\nlet cachedRequest: RequestFn | null = null;\n\n// Windows probe: where.exe is reliable for PATH lookup (exit 0 = found).\n// PowerShell 5.x doesn't accept --version, so the old Unix-style probe was\n// timing out and miscategorising every Windows install as \"no audio\".\nasync function isExecutableWin(cmd: string): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = spawn('where.exe', [cmd], { stdio: 'ignore', windowsHide: true });\n probe.on('error', () => resolve(false));\n probe.on('exit', (code) => resolve(code === 0));\n setTimeout(() => {\n probe.kill();\n resolve(false);\n }, 150);\n });\n}\n\nasync function isExecutableUnix(cmd: string): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = spawn(cmd, ['--version'], { stdio: 'ignore' });\n probe.on('error', () => resolve(false));\n probe.on('exit', () => resolve(true));\n setTimeout(() => {\n probe.kill();\n resolve(false);\n }, 150);\n });\n}\n\nasync function isExecutable(cmd: string): Promise<boolean> {\n return process.platform === 'win32' ? isExecutableWin(cmd) : isExecutableUnix(cmd);\n}\n\nasync function detect(): Promise<{ wav: Player | null; mp3: Player | null }> {\n // Filter by platform — no point probing afplay on Windows or pwsh on Linux.\n // Parallel probe within the filtered set keeps boot time at ~150ms regardless.\n const wantPlatform: 'unix' | 'win' = process.platform === 'win32' ? 'win' : 'unix';\n const eligible = CANDIDATES.filter((p) => p.platform === wantPlatform);\n const checks = await Promise.all(\n eligible.map(async (p) => [p, await isExecutable(p.cmd)] as const),\n );\n let wav: Player | null = null;\n let mp3: Player | null = null;\n for (const [p, ok] of checks) {\n if (!ok) continue;\n if (!wav && (p.supports === 'wav' || p.supports === 'both')) wav = p;\n if (!mp3 && (p.supports === 'mp3' || p.supports === 'both')) mp3 = p;\n if (wav && mp3) break;\n }\n return { wav, mp3 };\n}\n\nasync function loadPQueue(): Promise<PQueueCtor> {\n const mod = await import('p-queue');\n return mod.default;\n}\n\nexport async function initAudio(disabledByConfig: boolean): Promise<AudioRuntime> {\n if (runtime) return runtime;\n const PQueue = await loadPQueue();\n if (disabledByConfig) {\n runtime = {\n disabled: true,\n wavPlayer: null,\n mp3Player: null,\n warning: null,\n keyQueue: new PQueue({ concurrency: 1 }),\n feedbackQueue: new PQueue({ concurrency: 1 }),\n pronQueue: new PQueue({ concurrency: 1 }),\n };\n return runtime;\n }\n const { wav, mp3 } = await detect();\n let warning: string | null = null;\n if (!wav && !mp3) {\n warning =\n process.platform === 'win32'\n ? 'No audio player found on PATH (looked for pwsh/powershell). Install PowerShell or add ffplay to PATH. Sounds disabled.'\n : 'No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay). Sounds disabled.';\n } else if (!mp3) {\n warning = 'No MP3 player found; word pronunciations will be skipped.';\n }\n runtime = {\n disabled: !wav && !mp3,\n wavPlayer: wav,\n mp3Player: mp3,\n warning,\n keyQueue: new PQueue({ concurrency: 2 }),\n feedbackQueue: new PQueue({ concurrency: 1 }),\n pronQueue: new PQueue({ concurrency: 1 }),\n };\n return runtime;\n}\n\nfunction audioDebugEnabled(): boolean {\n const v = process.env.QWERTY_AUDIO_DEBUG;\n return !!v && v !== '0' && v.toLowerCase() !== 'false';\n}\n\nfunction audioDebugLogPath(): string {\n return join(paths.root, 'cache', 'audio-debug.log');\n}\n\nasync function appendAudioDebug(line: string): Promise<void> {\n // Best-effort write; never block the audio path and never surface errors.\n try {\n const p = audioDebugLogPath();\n await mkdir(dirname(p), { recursive: true });\n await appendFile(p, `${line}\\n`);\n } catch {\n /* ignore */\n }\n}\n\nfunction spawnPlay(player: Player, file: string, fileKind: 'wav' | 'mp3'): void {\n const debug = audioDebugEnabled();\n try {\n const args = player.args(file, fileKind);\n const child = spawn(player.cmd, args, {\n detached: true,\n stdio: debug ? ['ignore', 'pipe', 'pipe'] : 'ignore',\n windowsHide: true,\n });\n child.on('error', () => {\n // swallow; runtime will be disabled if many failures stack up\n });\n if (debug) {\n const ts = new Date().toISOString();\n const stderrChunks: Buffer[] = [];\n child.stderr?.on('data', (b: Buffer) => stderrChunks.push(b));\n child.stdout?.resume();\n child.on('exit', (code) => {\n const err = Buffer.concat(stderrChunks).toString('utf8').trim();\n void appendAudioDebug(\n `[${ts}] ${player.cmd} ${args.join(' ')} | exit=${code ?? 'null'}${err ? ` | stderr=${err.replace(/\\n/g, ' ')}` : ''}`,\n );\n });\n }\n child.unref();\n } catch {\n /* fail-soft */\n }\n}\n\nfunction playFile(file: string, kind: 'wav' | 'mp3'): void {\n if (!runtime || runtime.disabled) return;\n const player = kind === 'wav' ? runtime.wavPlayer : runtime.mp3Player;\n if (!player) return;\n spawnPlay(player, file, kind);\n}\n\nexport function playKeystroke(): void {\n if (!runtime || runtime.disabled) return;\n // Drop if queue is saturated; we never want to delay typing.\n if (runtime.keyQueue.size >= 2) return;\n void runtime.keyQueue.add(async () => {\n playFile(join(packageAssetsDir(), 'sounds', 'key-default.wav'), 'wav');\n await new Promise((r) => setTimeout(r, 30));\n });\n}\n\nexport function playCorrect(): void {\n if (!runtime || runtime.disabled) return;\n void runtime.feedbackQueue.add(async () => {\n playFile(join(packageAssetsDir(), 'sounds', 'correct.wav'), 'wav');\n await new Promise((r) => setTimeout(r, 50));\n });\n}\n\nexport function playWrong(): void {\n if (!runtime || runtime.disabled) return;\n void runtime.feedbackQueue.add(async () => {\n playFile(join(packageAssetsDir(), 'sounds', 'wrong.wav'), 'wav');\n await new Promise((r) => setTimeout(r, 50));\n });\n}\n\nasync function loadRequest(): Promise<RequestFn> {\n if (cachedRequest) return cachedRequest;\n const mod = await import('undici');\n cachedRequest = mod.request;\n return cachedRequest;\n}\n\nasync function downloadPronunciation(word: string, accent: 'us' | 'uk'): Promise<string | null> {\n const cacheFile = paths.audioCache(word, accent);\n if (await exists(cacheFile)) return cacheFile;\n await mkdir(dirname(cacheFile), { recursive: true });\n const type = accent === 'us' ? 2 : 1;\n const url = `${PRON_API}${encodeURIComponent(word)}&type=${type}`;\n try {\n const request = await loadRequest();\n const res = await request(url, { headersTimeout: 8000, bodyTimeout: 20000 });\n if (res.statusCode >= 400) return null;\n const buf = Buffer.from(await res.body.arrayBuffer());\n if (buf.length < 1024) return null;\n const tmp = `${cacheFile}.tmp`;\n await writeFile(tmp, buf);\n await rename(tmp, cacheFile);\n return cacheFile;\n } catch {\n return null;\n }\n}\n\nexport async function playPronunciation(word: string, accent: 'us' | 'uk'): Promise<void> {\n if (!runtime || runtime.disabled || !runtime.mp3Player) return;\n await ensureDirs();\n await runtime.pronQueue.add(async () => {\n const file = await downloadPronunciation(word, accent);\n if (file) playFile(file, 'mp3');\n });\n}\n\nexport async function prefetchPronunciation(word: string, accent: 'us' | 'uk'): Promise<void> {\n if (!runtime || runtime.disabled || !runtime.mp3Player) return;\n await ensureDirs();\n void runtime.pronQueue.add(async () => {\n await downloadPronunciation(word, accent);\n });\n}\n\nexport function audioWarning(): string | null {\n return runtime?.warning ?? null;\n}\n\nexport function audioDisabled(): boolean {\n return runtime?.disabled ?? true;\n}\n\nexport type PlayerSummary = { kind: PlayerKind; cmd: string } | null;\n\nexport function audioPlayers(): { wav: PlayerSummary; mp3: PlayerSummary } {\n const w = runtime?.wavPlayer;\n const m = runtime?.mp3Player;\n return {\n wav: w ? { kind: w.kind, cmd: w.cmd } : null,\n mp3: m ? { kind: m.kind, cmd: m.cmd } : null,\n };\n}\n\n// Exposed for unit tests only — verifies PowerShell command construction and\n// candidate priority order.\nexport const __test = {\n buildPwshArgs,\n escapePwshPath,\n CANDIDATES,\n};\n"],"mappings":"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","names":["spawn","appendFile","mkdir","rename","writeFile","join","dirname","escapePwshPath","file","buildPwshArgs","fileKind","escaped","CANDIDATES","f","PRON_API","runtime","cachedRequest","isExecutableWin","cmd","resolve","probe","spawn","code","isExecutableUnix","isExecutable","detect","wantPlatform","eligible","p","checks","wav","mp3","ok","loadPQueue","initAudio","disabledByConfig","PQueue","warning","audioDebugEnabled","v","audioDebugLogPath","join","paths","appendAudioDebug","line","mkdir","dirname","appendFile","spawnPlay","player","debug","args","child","ts","stderrChunks","b","err","playFile","kind","playKeystroke","packageAssetsDir","r","playCorrect","playWrong","loadRequest","downloadPronunciation","word","accent","cacheFile","exists","type","url","res","buf","tmp","writeFile","rename","playPronunciation","ensureDirs","prefetchPronunciation","audioWarning","audioDisabled","audioPlayers","w","m"]}
@@ -0,0 +1,2 @@
1
+ import{createContext as k,useContext as E,useState as L,useCallback as g}from"react";import{jsx as v}from"react/jsx-runtime";var h=k(null);function N({initial:t,children:e}){let[n,r]=L([t]),s=g(i=>{r(o=>[...o,i])},[]),a=g(i=>{r(o=>o.length===0?[i]:[...o.slice(0,-1),i])},[]),u=g(()=>{r(i=>i.length>1?i.slice(0,-1):i)},[]),c=g(i=>{r([i])},[]),l=n[n.length-1];return v(h.Provider,{value:{current:l,stack:n,navigate:s,replace:a,back:u,reset:c},children:e})}function D(){let t=E(h);if(!t)throw new Error("useNav must be used inside NavProvider");return t}import{createContext as x,useContext as S,useMemo as $}from"react";var d={app:{title:"qwerty",subtitle:"typing practice for the terminal"},common:{back:"back",quit:"quit",on:"on",off:"off",cancel:"cancel"},mainMenu:{items:{practiceLabel:"Practice",practiceHintWith:t=>`start ${t}`,practiceHintNone:"pick a dictionary",dictLabel:"Dictionaries",dictHint:"browse, pull, set default",wordLabel:"Word lookup",wordHint:"search local dicts",statsLabel:"Stats",statsHint:"history & trends",configLabel:"Config",configHint:"edit preferences",stealthLabel:"Stealth",stealthHint:"quiet practice mode",quitLabel:"Quit",quitHint:"Esc or Ctrl+C also exits"},hint:"\u2191/\u2193 navigate \xB7 Enter select \xB7 letters jump",helpHint:"? help"},dict:{title:"Dictionaries",loading:"loading dictionaries\u2026",entries:t=>`${t} entries`,filterPlaceholder:"type to filter",local:"local \u2713",notLocal:"not local",defaultMark:"default \u2605",tagsLabel:t=>`tags: ${t}`,wordsLabel:t=>`${t} words`,pulling:t=>`pulling ${t}\u2026`,removing:t=>`removing ${t}\u2026`,errorOn:(t,e)=>`error on ${t}: ${e}`,footer:"\u2191/\u2193 select \xB7 Enter actions \xB7 Ctrl+K more \xB7 Esc back",action:{title:"current dictionary",setDefault:"set as default",practice:"practice now",delete:"delete local"},command:{title:"more actions",pull:"pull selected",import:"import .json",refreshList:"update dictionary list"}},config:{title:"Config",fields:{defaultDict:"default dict",defaultMode:"default mode",accent:"accent",mirror:"dict mirror",chapterSize:"chapter size",autoplayPronunciation:"autoplay pronunciation",soundsMaster:"sounds master",soundsKeystroke:"sounds keystroke",soundsFeedback:"sounds feedback",soundsKeySound:"sounds key sound",language:"language",stealth:"stealth mode"},enumValues:{stealth:{off:"off",menu:"show in menu",default:"default practice"}},hints:{editing:"type to edit \xB7 Enter save \xB7 Esc cancel",bool:"space toggle \xB7 \u2191/\u2193 move \xB7 Esc back",enum:"\u2190/\u2192 cycle \xB7 \u2191/\u2193 move \xB7 Esc back",dictRef:"Enter pick dict \xB7 \u2191/\u2193 move \xB7 Esc back",stringOrInt:"Enter edit \xB7 \u2191/\u2193 move \xB7 Esc back"}},stats:{title:"Stats \xB7 overview",loading:"loading stats\u2026",none:"No practice history yet.",nonePractice:"Run a practice session first.",lifetime:"lifetime",sessions:"sessions",words:"words",errors:"errors",wpm:"wpm",accuracy:"accuracy",streak:"streak",last:t=>`last ${t} days (\u2190/\u2192 cycle window)`,cycleWindow:"\u2190/\u2192 cycle window \xB7 Esc back",recent:"recent sessions",topMistakes:"top mistakes",footer:"\u2190/\u2192 cycle window \xB7 Esc back",maxLabel:"max",recentUnits:{words:"w",errors:"err",wpm:"wpm"},multiDictSuffix:t=>` +${t} more`,bars:{speed:"speed",accuracy:"accuracy",sessions:"sessions"}},word:{title:"Word lookup",indexing:"indexing local dictionaries\u2026",none:"No local dictionaries.",pullFirst:"Pull one in Dictionaries first.",countAcross:t=>`${t} words across local dicts`,noMatches:t=>`no matches for "${t}"`,inDict:t=>`in: ${t}`,mistakes:(t,e)=>`mistakes: ${t} (last ${e})`,footer:"type to filter \xB7 \u2191/\u2193 select \xB7 Esc back"},practice:{loading:"loading\u2026",paused:"PAUSED",chapterComplete:"CHAPTER COMPLETE",chapterLabel:(t,e)=>`chapter ${t}/${e}`,reviewLabel:"review",statusBar:{mode:"mode",accent:"accent"},modes:{order:"order",dictation:"dictation",review:"review",random:"random",loop:"loop"},accents:{us:"us",uk:"uk"},statCards:{words:"words",errors:"errors",wpm:"wpm",accuracy:"accuracy",elapsed:t=>`elapsed ${t}`},pause:{title:"PAUSED",chapter:(t,e)=>`chapter ${t}/${e}`,progress:(t,e)=>`${t}/${e}`,hint:"Enter resume \xB7 Esc back to menu"},summary:{loopAgain:"again",nextChapter:"next chapter",reviewMistakes:"review mistakes",backMenu:"back to menu"},footers:{typing:"Ctrl+N skip \xB7 Esc pause \xB7 Tab replay"},errors:{noMistakes:"No mistakes to review yet. Practice some chapters first.",dictEmpty:t=>`Dictionary ${t} is empty.`,unknown:"Unknown error"},imeWarning:"! Non-English input detected \u2014 switch your IME to English.",imeWarningShort:"! IME",audioWarningShort:"! audio"},audio:{noPlayer:"! No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay/powershell). Sounds disabled."},report:{title:"Session summary",duration:"duration",practiced:"practiced",chapters:"chapters",words:"words",accuracy:"accuracy",wpm:"wpm",newMistakes:"new mistakes",farewell:"see you next time.",notPracticed:"no practice this run"},help:{title:"Help",subtitle:"all shortcuts",sections:{main:"main menu",practice:"practice",dict:"dictionaries",config:"config",stats:"stats",word:"word lookup",global:"global"},keys:{navigate:"\u2191/\u2193 navigate items",select:"Enter confirm / continue",letterJump:"letter jump to menu item",pause:"Esc pause practice",skip:"Ctrl+N skip current word (neutral)",replay:"Tab replay pronunciation",resume:"Enter resume from pause",backMenu:"Esc back to previous screen",backScreen:"Esc close panel or back",nextChapter:"Enter next chapter",reviewMistakes:"m review mistakes",filter:"type to filter list",itemActions:"Enter open actions panel",moreActions:"Ctrl+K more actions panel",cycleWindow:"\u2190/\u2192 cycle day window",stealthToggle:"Ctrl+I toggle stealth info row",helpScreen:"? open this help screen",quit:"Ctrl+C quit immediately"},footer:"Esc back"},stealth:{paused:"paused",chapterDone:"chapter done",resumeHint:"Enter resume \xB7 Esc menu",nextHint:"Enter next \xB7 Esc menu",pausedHintRight:"Enter resume",nextHintRight:"Enter next",infoChipLabel:"info",infoFmt:(t,e,n,r,s,a)=>`${t} \xB7 ${e} \xB7 ${n}/${r} \xB7 ${s} wpm \xB7 ${a}%`}},p={app:{title:"qwerty",subtitle:"\u7EC8\u7AEF\u952E\u76D8\u7EC3\u4E60"},common:{back:"\u8FD4\u56DE",quit:"\u9000\u51FA",on:"\u5F00",off:"\u5173",cancel:"\u53D6\u6D88"},mainMenu:{items:{practiceLabel:"\u7EC3\u4E60",practiceHintWith:t=>`\u5F00\u59CB ${t}`,practiceHintNone:"\u8BF7\u5148\u9009\u8BCD\u5178",dictLabel:"\u8BCD\u5178",dictHint:"\u6D4F\u89C8\u3001\u4E0B\u8F7D\u3001\u8BBE\u4E3A\u9ED8\u8BA4",wordLabel:"\u67E5\u8BCD",wordHint:"\u5728\u672C\u5730\u8BCD\u5178\u4E2D\u641C\u7D22",statsLabel:"\u7EDF\u8BA1",statsHint:"\u5386\u53F2\u4E0E\u8D8B\u52BF",configLabel:"\u8BBE\u7F6E",configHint:"\u4FEE\u6539\u504F\u597D",stealthLabel:"\u795E\u9690",stealthHint:"\u795E\u9690\u7EC3\u4E60\u6A21\u5F0F",quitLabel:"\u9000\u51FA",quitHint:"Esc \u6216 Ctrl+C \u9000\u51FA"},hint:"\u2191/\u2193 \u79FB\u52A8 \xB7 Enter \u786E\u8BA4 \xB7 \u5B57\u6BCD\u76F4\u8FBE",helpHint:"? \u5E2E\u52A9"},dict:{title:"\u8BCD\u5178",loading:"\u52A0\u8F7D\u8BCD\u5178\u4E2D\u2026",entries:t=>`${t} \u90E8\u8BCD\u5178`,filterPlaceholder:"\u8F93\u5165\u8FC7\u6EE4",local:"\u5DF2\u4E0B\u8F7D \u2713",notLocal:"\u672A\u4E0B\u8F7D",defaultMark:"\u9ED8\u8BA4 \u2605",tagsLabel:t=>`\u6807\u7B7E:${t}`,wordsLabel:t=>`${t} \u8BCD`,pulling:t=>`\u62C9\u53D6 ${t} \u4E2D\u2026`,removing:t=>`\u5220\u9664 ${t} \u4E2D\u2026`,errorOn:(t,e)=>`${t} \u51FA\u9519:${e}`,footer:"\u2191/\u2193 \u9009\u62E9 \xB7 Enter \u64CD\u4F5C \xB7 Ctrl+K \u66F4\u591A \xB7 Esc \u8FD4\u56DE",action:{title:"\u5F53\u524D\u8BCD\u5178",setDefault:"\u8BBE\u4E3A\u9ED8\u8BA4",practice:"\u7ACB\u5373\u7EC3\u4E60",delete:"\u5220\u9664\u672C\u5730"},command:{title:"\u66F4\u591A\u529F\u80FD",pull:"\u62C9\u53D6\u9009\u4E2D",import:"\u5BFC\u5165 .json",refreshList:"\u66F4\u65B0\u8BCD\u5178\u5217\u8868"}},config:{title:"\u8BBE\u7F6E",fields:{defaultDict:"\u9ED8\u8BA4\u8BCD\u5178",defaultMode:"\u9ED8\u8BA4\u6A21\u5F0F",accent:"\u53D1\u97F3",mirror:"\u8BCD\u5178\u955C\u50CF\u6E90",chapterSize:"\u7AE0\u8282\u5355\u8BCD\u6570",autoplayPronunciation:"\u81EA\u52A8\u64AD\u653E\u53D1\u97F3",soundsMaster:"\u97F3\u6548\u603B\u5F00\u5173",soundsKeystroke:"\u6309\u952E\u97F3",soundsFeedback:"\u53CD\u9988\u97F3",soundsKeySound:"\u6309\u952E\u97F3\u8272",language:"\u8BED\u8A00",stealth:"\u795E\u9690\u6A21\u5F0F"},enumValues:{stealth:{off:"\u5173\u95ED",menu:"\u4E3B\u83DC\u5355\u663E\u793A",default:"\u9ED8\u8BA4\u7EC3\u4E60\u6A21\u5F0F"}},hints:{editing:"\u8F93\u5165\u4FEE\u6539 \xB7 Enter \u4FDD\u5B58 \xB7 Esc \u53D6\u6D88",bool:"\u7A7A\u683C\u5207\u6362 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",enum:"\u2190/\u2192 \u5207\u6362 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",dictRef:"Enter \u9009\u8BCD\u5178 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",stringOrInt:"Enter \u7F16\u8F91 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE"}},stats:{title:"\u7EDF\u8BA1 \xB7 \u6982\u89C8",loading:"\u52A0\u8F7D\u7EDF\u8BA1\u4E2D\u2026",none:"\u8FD8\u6CA1\u6709\u7EC3\u4E60\u8BB0\u5F55\u3002",nonePractice:"\u5148\u6765\u4E00\u6B21\u7EC3\u4E60\u5427\u3002",lifetime:"\u7D2F\u8BA1",sessions:"\u4F1A\u8BDD",words:"\u8BCD\u6570",errors:"\u9519\u8BEF",wpm:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",streak:"\u8FDE\u7EED\u5929\u6570",last:t=>`\u6700\u8FD1 ${t} \u5929 (\u2190/\u2192 \u5207\u6362\u7A97\u53E3)`,cycleWindow:"\u2190/\u2192 \u5207\u6362\u7A97\u53E3 \xB7 Esc \u8FD4\u56DE",recent:"\u6700\u8FD1\u4F1A\u8BDD",topMistakes:"\u9AD8\u9891\u9519\u8BCD",footer:"\u2190/\u2192 \u5207\u6362\u7A97\u53E3 \xB7 Esc \u8FD4\u56DE",maxLabel:"\u6700\u5927",recentUnits:{words:"\u8BCD",errors:"\u9519",wpm:"\u901F"},multiDictSuffix:t=>` \u7B49 ${t} \u90E8`,bars:{speed:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",sessions:"\u4F1A\u8BDD"}},word:{title:"\u67E5\u8BCD",indexing:"\u7D22\u5F15\u672C\u5730\u8BCD\u5178\u4E2D\u2026",none:"\u6CA1\u6709\u672C\u5730\u8BCD\u5178\u3002",pullFirst:"\u5148\u5728\u300C\u8BCD\u5178\u300D\u4E2D\u62C9\u53D6\u4E00\u90E8\u3002",countAcross:t=>`\u672C\u5730\u8BCD\u5178\u5171 ${t} \u8BCD`,noMatches:t=>`\u6CA1\u6709\u5339\u914D\u300C${t}\u300D\u7684\u8BCD`,inDict:t=>`\u6765\u6E90:${t}`,mistakes:(t,e)=>`\u9519\u8FC7 ${t} \u6B21 (\u6700\u8FD1 ${e})`,footer:"\u8F93\u5165\u8FC7\u6EE4 \xB7 \u2191/\u2193 \u9009\u62E9 \xB7 Esc \u8FD4\u56DE"},practice:{loading:"\u52A0\u8F7D\u4E2D\u2026",paused:"\u5DF2\u6682\u505C",chapterComplete:"\u672C\u7AE0\u5B8C\u6210",chapterLabel:(t,e)=>`\u7B2C ${t}/${e} \u7AE0`,reviewLabel:"\u590D\u4E60",statusBar:{mode:"\u6A21\u5F0F",accent:"\u53D1\u97F3"},modes:{order:"\u987A\u5E8F",dictation:"\u9ED8\u5199",review:"\u590D\u4E60",random:"\u4E71\u5E8F",loop:"\u5FAA\u73AF"},accents:{us:"\u7F8E",uk:"\u82F1"},statCards:{words:"\u8BCD\u6570",errors:"\u9519\u8BEF",wpm:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",elapsed:t=>`\u8017\u65F6 ${t}`},pause:{title:"\u5DF2\u6682\u505C",chapter:(t,e)=>`\u7B2C ${t}/${e} \u7AE0`,progress:(t,e)=>`${t}/${e}`,hint:"Enter \u7EE7\u7EED \xB7 Esc \u8FD4\u56DE\u83DC\u5355"},summary:{loopAgain:"\u518D\u6765\u4E00\u904D",nextChapter:"\u4E0B\u4E00\u7AE0",reviewMistakes:"\u590D\u4E60\u9519\u8BCD",backMenu:"\u8FD4\u56DE\u83DC\u5355"},footers:{typing:"Ctrl+N \u8DF3\u8FC7 \xB7 Esc \u6682\u505C \xB7 Tab \u91CD\u64AD"},errors:{noMistakes:"\u9519\u8BCD\u672C\u662F\u7A7A\u7684\u3002\u5148\u7EC3\u4E60\u51E0\u7AE0\u5427\u3002",dictEmpty:t=>`\u8BCD\u5178 ${t} \u662F\u7A7A\u7684\u3002`,unknown:"\u672A\u77E5\u9519\u8BEF"},imeWarning:"! \u68C0\u6D4B\u5230\u4E2D\u6587/\u65E5\u6587/\u97E9\u6587\u8F93\u5165\uFF0C\u8BF7\u5207\u6362\u5230\u82F1\u6587\u8F93\u5165\u6CD5",imeWarningShort:"! \u4E2D\u6587\u8F93\u5165",audioWarningShort:"! \u65E0\u97F3\u6548"},audio:{noPlayer:"! \u672A\u5728 PATH \u4E2D\u627E\u5230\u97F3\u9891\u64AD\u653E\u5668(\u5C1D\u8BD5 afplay/ffplay/mpg123/paplay/aplay/powershell)\u3002\u97F3\u6548\u5DF2\u7981\u7528\u3002"},report:{title:"\u672C\u6B21\u4F1A\u8BDD",duration:"\u603B\u65F6\u957F",practiced:"\u7EC3\u4E60\u7528\u65F6",chapters:"\u5B8C\u6210\u7AE0\u8282",words:"\u8BCD\u6570",accuracy:"\u51C6\u786E\u7387",wpm:"\u901F\u5EA6",newMistakes:"\u65B0\u9519\u8BCD",farewell:"\u4E0B\u6B21\u89C1\u3002",notPracticed:"\u672C\u6B21\u672A\u7EC3\u4E60"},help:{title:"\u5E2E\u52A9",subtitle:"\u5168\u90E8\u5FEB\u6377\u952E",sections:{main:"\u4E3B\u83DC\u5355",practice:"\u7EC3\u4E60",dict:"\u8BCD\u5178",config:"\u8BBE\u7F6E",stats:"\u7EDF\u8BA1",word:"\u67E5\u8BCD",global:"\u5168\u5C40"},keys:{navigate:"\u2191/\u2193 \u79FB\u52A8\u9009\u9879",select:"Enter \u786E\u8BA4 / \u7EE7\u7EED",letterJump:"\u5B57\u6BCD\u952E \u76F4\u8FBE\u83DC\u5355\u9879",pause:"Esc \u6682\u505C\u7EC3\u4E60",skip:"Ctrl+N \u8DF3\u8FC7\u5F53\u524D\u8BCD(\u4E0D\u8BA1\u9519)",replay:"Tab \u91CD\u64AD\u53D1\u97F3",resume:"Enter \u7EE7\u7EED\u7EC3\u4E60",backMenu:"Esc \u8FD4\u56DE\u4E0A\u4E00\u5C4F",backScreen:"Esc \u5173\u95ED\u9762\u677F / \u8FD4\u56DE",nextChapter:"Enter \u4E0B\u4E00\u7AE0",reviewMistakes:"m \u590D\u4E60\u9519\u8BCD",filter:"\u8F93\u5165 \u8FC7\u6EE4\u5217\u8868",itemActions:"Enter \u5F39\u51FA\u52A8\u4F5C\u9762\u677F",moreActions:"Ctrl+K \u5F39\u51FA\u66F4\u591A\u529F\u80FD",cycleWindow:"\u2190/\u2192 \u5207\u6362\u65E5\u7A97\u53E3",stealthToggle:"Ctrl+I \u5207\u6362\u795E\u9690\u4FE1\u606F\u884C",helpScreen:"? \u6253\u5F00\u672C\u5E2E\u52A9\u9875",quit:"Ctrl+C \u7ACB\u5373\u9000\u51FA"},footer:"Esc \u8FD4\u56DE"},stealth:{paused:"paused",chapterDone:"chapter done",resumeHint:"Enter resume \xB7 Esc menu",nextHint:"Enter next \xB7 Esc menu",pausedHintRight:"Enter \u7EE7\u7EED",nextHintRight:"Enter \u4E0B\u4E00\u7AE0",infoChipLabel:"\u4FE1\u606F",infoFmt:(t,e,n,r,s,a)=>`${t} \xB7 ${e} \xB7 ${n}/${r} \xB7 ${s} wpm \xB7 ${a}%`}};function b(t){if(!t)return null;let e=t.toLowerCase();return e.startsWith("zh")?"zh":e.startsWith("en")?"en":null}function m(t){if(t==="zh"||t==="en")return t;let e=process.env.LC_ALL||process.env.LC_MESSAGES||process.env.LANG||process.env.LANGUAGE,n=b(e);if(n)return n;try{let r=Intl.DateTimeFormat().resolvedOptions().locale,s=b(r);if(s)return s}catch{}return"en"}import{jsx as C}from"react/jsx-runtime";var w=x(null);function I({pref:t,children:e}){let n=$(()=>{let r=m(t);return{lang:r,t:r==="zh"?p:d}},[t]);return C(w.Provider,{value:n,children:e})}function K(){let t=S(w);if(!t)throw new Error("useStrings must be used inside StringsProvider");return t.t}function O(t){let e=m(t);return{lang:e,t:e==="zh"?p:d}}import{Box as P,Text as H}from"ink";import{jsx as y}from"react/jsx-runtime";var f={accent:"#5eead4",muted:"#6b7280",text:"#e5e7eb",primary:"#7dcfff",success:"#86efac",warning:"#fbbf24",error:"#f87171"};function G({target:t,typed:e,error:n=!1,hideTarget:r=!1}){let s=[...t],a=[...e];return y(P,{paddingY:4,justifyContent:"center",children:s.map((u,c)=>{let l=c<a.length,i=r&&!l?"_":l?a[c]:u,o=n?f.error:l?f.accent:f.muted;return y(H,{bold:!0,color:o,children:i},c)})})}export{N as a,D as b,I as c,K as d,O as e,f,G as g};
2
+ //# sourceMappingURL=chunk-R6HQWKXU.js.map