diff --git a/README.md b/README.md
index d08d1fc..69f53f9 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,13 @@
## Singleplayer
-- [ ] Restart game after solving the stage
+- [x] Restart game after solving the stage
- [ ] Add events for settings panel
- [ ] Seed-based board generation
- [ ] Numbered stages(infinitely many)
- [ ] Speech API to speak out completed word
+- [ ] Cheat prevention (browser find feature)
+ - [ ] pointer position based tracking instead of using element hover state
- [ ] Time the stage completion
- [ ] Share API to share the result time
diff --git a/css.css b/css.css
index d0ddaea..99c7dd8 100644
--- a/css.css
+++ b/css.css
@@ -1,2 +1,2 @@
-body{display:flex;flex-direction:column;justify-content:center;align-items:center}.hidden{position:absolute;width:0;height:0;pointer-events:none;opacity:0;user-select:none;overflow:hidden}#jamo-board,#word-list{display:grid;padding:0;margin:0}#word-list{grid-template-columns:repeat(4,1fr);gap:1rem;list-style:none}#jamo-board>.written,#word-list>.found{background-color:var(--color)}#jamo-board{grid-template-columns:repeat(var(--width),1fr);gap:var(--gap);position:relative;aspect-ratio:1;touch-action:none;-webkit-tap-highlight-color:transparent}#jamo-board>i{text-align:center;display:inline-block;width:var(--size);height:var(--size);font-size:var(--size);line-height:1;user-select:none;cursor:pointer;font-style:normal;position:relative}:root[data-mode=dark] #jamo-board>i::before{background-color:#fff}:root[data-mode=light] #jamo-board>i::before{background-color:#000}:root[data-mode=light] #jamo-board>.completion-bar::before{opacity:.6}@media (prefers-color-scheme:dark){:root[data-mode=system] #jamo-board>i::before{background-color:#fff}}@media (prefers-color-scheme:light){:root[data-mode=system] #jamo-board>i::before{background-color:#000}:root[data-mode=system] #jamo-board>.completion-bar::before{opacity:.6}}#jamo-board>i::before{content:"";display:inline-block;width:calc(var(--size)*1.414);height:calc(var(--size)*1.414);top:50%;left:50%;border-radius:50%;pointer-events:none;opacity:0;position:absolute;transform:translate(-50%,-50%);z-index:-1}#jamo-board:not(:active)>i:hover::before{opacity:.15}#jamo-board:active>i{padding:calc(var(--gap)/4);margin:calc(var(--gap)/-4)}#jamo-board>.completion-bar{position:absolute;top:var(--top);left:var(--left);width:var(--width);height:var(--height);pointer-events:none}#jamo-board>.completion-bar::before{content:"";position:absolute;background-color:var(--color);height:var(--thick);top:50%;left:50%;width:var(--hypot);transform:translate(-50%,-50%) rotate(var(--angle));transform-origin:center;mix-blend-mode:overlay;opacity:.5;z-index:-2;border-radius:var(--thick)}nav{height:2rem}#dark-mode-toggle{position:absolute;z-index:1;top:1rem;right:1rem;width:2rem;height:2rem;display:flex;justify-content:center;align-items:center;cursor:pointer;transform:rotate(45deg);border-radius:50%;overflow:hidden;padding:0;view-transition-name:none;-webkit-tap-highlight-color:transparent}.notransition,.notransition>*{transition:none!important}#dark-mode-toggle>#moon,#dark-mode-toggle>#sun{position:absolute;font-size:1.5rem;line-height:1;text-align:center;top:0;left:0;width:100%;height:100%;overflow:hidden;transition:clip-path .5s;color-scheme:light;background-color:buttonface}#dark-mode-toggle>#moon{color-scheme:dark}#dark-mode-toggle>#moon::before,#dark-mode-toggle>#sun::before{display:inline-block;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-45deg)}#dark-mode-toggle>#sun::before{content:"โ๏ธ"}#dark-mode-toggle>#moon::before{content:"๐"}:root[data-mode=system]{color-scheme:dark light}:root[data-mode=dark]{color-scheme:dark}:root[data-mode=light]{color-scheme:light}#dark-mode-toggle[data-mode=system]>#sun{clip-path:rect(0 50% 100%0)}#dark-mode-toggle[data-mode=system]>#moon{clip-path:rect(0 100% 100% 50%)}#dark-mode-toggle[data-mode=dark]>#sun{clip-path:rect(0 0 100%0)}#dark-mode-toggle[data-mode=dark]>#moon,#dark-mode-toggle[data-mode=light]>#sun{clip-path:rect(0 100% 100%0)}#dark-mode-toggle[data-mode=light]>#moon{clip-path:rect(0 100% 100% 100%)}#currentJamoCompletions{position:absolute}@media screen and (min-width:720px){#word-list{animation:none}}main{font-size:0}main>*{font-size:1rem}.noresize{resize:none}#file-wrapper{display:inline-block;position:relative;width:150px;height:100px;text-align:center;border:1px solid buttonborder;border-radius:2px}#file-wrapper:where(:focus-visible,:focus-within){outline:-webkit-focus-ring-color auto 1px}#select-game-state-file{cursor:pointer;position:absolute;top:0;left:0;width:100%;height:100%;opacity:0}#file-wrapper::before{content:"๐";font-size:50px;display:block;margin-bottom:10px}#file-wrapper::after{content:"ํ์ผ ๋ถ๋ฌ์ค๊ธฐ";display:block;font-size:14px}#settings-panel::backdrop{background:#0004}
+body{display:flex;flex-direction:column;justify-content:center;align-items:center}.hidden{position:absolute;width:0;height:0;pointer-events:none;opacity:0;user-select:none;overflow:hidden}#jamo-board,#word-list{display:grid;padding:0;margin:0}#word-list{grid-template-columns:repeat(4,1fr);gap:1rem;list-style:none}#jamo-board>.written,#word-list>.found{background-color:var(--color)}#jamo-board{grid-template-columns:repeat(var(--width),1fr);gap:var(--gap);position:relative;aspect-ratio:1;touch-action:none;-webkit-tap-highlight-color:transparent}#jamo-board>i{text-align:center;display:inline-block;width:var(--size);height:var(--size);font-size:var(--size);line-height:1;user-select:none;cursor:pointer;font-style:normal;position:relative}:root[data-mode=dark] #jamo-board>i::before{background-color:#fff}:root[data-mode=light] #jamo-board>i::before{background-color:#000}:root[data-mode=light] #jamo-board>.completion-bar::before{opacity:.6}@media (prefers-color-scheme:dark){:root[data-mode=system] #jamo-board>i::before{background-color:#fff}}@media (prefers-color-scheme:light){:root[data-mode=system] #jamo-board>i::before{background-color:#000}:root[data-mode=system] #jamo-board>.completion-bar::before{opacity:.6}}#jamo-board>i::before{content:"";display:inline-block;width:calc(var(--size)*1.414);height:calc(var(--size)*1.414);top:50%;left:50%;border-radius:50%;pointer-events:none;opacity:0;position:absolute;transform:translate(-50%,-50%);z-index:-1}#jamo-board:not(:active)>i:hover::before{opacity:.15}#jamo-board:active>i{padding:calc(var(--gap)/4);margin:calc(var(--gap)/-4)}#jamo-board>.completion-bar{position:absolute;top:var(--top);left:var(--left);width:var(--width);height:var(--height);pointer-events:none}#jamo-board>.completion-bar::before{content:"";position:absolute;background-color:var(--color);height:var(--thick);top:50%;left:50%;width:var(--hypot);transform:translate(-50%,-50%) rotate(var(--angle));transform-origin:center;mix-blend-mode:overlay;opacity:.5;z-index:-2;border-radius:var(--thick)}nav{height:2rem}#dark-mode-toggle{position:absolute;z-index:1;top:1rem;right:1rem;width:2rem;height:2rem;display:flex;justify-content:center;align-items:center;cursor:pointer;transform:rotate(45deg);border-radius:50%;overflow:hidden;padding:0;view-transition-name:none;-webkit-tap-highlight-color:transparent}.notransition,.notransition>*{transition:none!important}#dark-mode-toggle>#moon,#dark-mode-toggle>#sun{position:absolute;font-size:1.5rem;line-height:1;text-align:center;top:0;left:0;width:100%;height:100%;overflow:hidden;transition:clip-path .5s;color-scheme:light;background-color:buttonface}#dark-mode-toggle>#moon{color-scheme:dark}#dark-mode-toggle>#moon::before,#dark-mode-toggle>#sun::before{display:inline-block;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-45deg)}#dark-mode-toggle>#sun::before{content:"โ๏ธ"}#dark-mode-toggle>#moon::before{content:"๐"}:root[data-mode=system]{color-scheme:dark light}:root[data-mode=dark]{color-scheme:dark}:root[data-mode=light]{color-scheme:light}#dark-mode-toggle[data-mode=system]>#sun{clip-path:rect(0 50% 100%0)}#dark-mode-toggle[data-mode=system]>#moon{clip-path:rect(0 100% 100% 50%)}#dark-mode-toggle[data-mode=dark]>#sun{clip-path:rect(0 0 100%0)}#dark-mode-toggle[data-mode=dark]>#moon,#dark-mode-toggle[data-mode=light]>#sun{clip-path:rect(0 100% 100%0)}#dark-mode-toggle[data-mode=light]>#moon{clip-path:rect(0 100% 100% 100%)}#currentJamoCompletions{position:absolute}@media screen and (min-width:720px){#word-list{animation:none}}main{font-size:0}main>*{font-size:1rem}.noresize{resize:none}#file-wrapper{display:inline-block;position:relative;width:150px;height:100px;text-align:center;border:1px solid buttonborder;border-radius:2px}#file-wrapper:where(:focus-visible,:focus-within){outline:-webkit-focus-ring-color auto 1px}#select-game-state-file{cursor:pointer;position:absolute;top:0;left:0;width:100%;height:100%;opacity:0}#file-wrapper::before{content:"๐";font-size:50px;display:block;margin-bottom:10px}#file-wrapper::after{content:attr(data-text);display:block;font-size:14px}#settings-panel::backdrop{background:#0004}
/*# sourceMappingURL=css.css.map */
\ No newline at end of file
diff --git a/css.css.map b/css.css.map
index f7ebdc7..2cdd974 100644
--- a/css.css.map
+++ b/css.css.map
@@ -1 +1 @@
-{"version":3,"sources":["src/css.css"],"names":[],"mappings":"AAEA,I,CACE,Y,CACA,qB,CACA,sB,CACA,kB,CAGF,O,CACE,iB,CACA,O,CACA,Q,CACA,mB,CACA,S,CACA,gB,CACA,e,CAkBF,W,CAdA,U,CAeE,Y,CAGA,S,CACA,Q,CAnBF,U,CAEE,mC,CACA,Q,CACA,e,CAiFF,oB,CA3EA,iB,CACE,6B,CAGF,W,CAEE,8C,CACA,c,CAGA,iB,CACA,c,CACA,iB,CACA,uC,CAGF,a,CACE,iB,CACA,oB,CACA,iB,CACA,kB,CACA,qB,CACA,a,CACA,gB,CACA,c,CACA,iB,CACA,iB,CAGF,2C,CACE,qB,CAEF,4C,CACE,qB,CAEF,0D,CACE,U,CAEF,mCACE,6C,CACE,uBAGJ,A,oCACE,6C,CACE,qB,CAEF,2D,CACE,YAIJ,qB,CACE,U,CACA,oB,CACA,6B,CACA,8B,CACA,O,CACA,Q,CACA,iB,CACA,mB,CACA,S,CACA,iB,CACA,8B,CACA,U,CAEF,gBAAgB,wB,CACd,W,CAGF,oB,CACE,0B,CACA,0B,CAOF,2B,CAEE,iB,CACA,c,CACA,gB,CACA,kB,CACA,oB,CAGA,mB,CAGF,mC,CAEE,U,CACA,iB,CACA,6B,CACA,mB,CAGA,O,CACA,Q,CAGA,kB,CAGA,mD,CAGA,uB,CACA,sB,CACA,U,CAEA,U,CAEA,0B,CAKF,G,CACE,W,CAGF,iB,CACE,iB,CACA,S,CACA,Q,CACA,U,CACA,U,CACA,W,CACA,Y,CACA,sB,CACA,kB,CACA,c,CACA,uB,CACA,iB,CACA,e,CACA,S,CACA,yB,CACA,uC,CAGF,a,CAAe,e,CACb,yB,CAGsB,uB,CAAxB,sB,CACE,iB,CACA,gB,CACA,a,CACA,iB,CACA,K,CACA,M,CACA,U,CACA,W,CACA,e,CACA,wB,CAIA,kB,CACA,2B,CAGF,uB,CACE,iB,CAKF,+B,CADA,8B,CAEE,oB,CACA,iB,CACA,O,CACA,Q,CACA,6C,CAGF,8B,CACE,Y,CAGF,+B,CACE,Y,CAGF,uB,CACE,uB,CAGF,qB,CACE,iB,CAGF,sB,CACE,kB,CAGF,wC,CAEE,2B,CAEF,yC,CAEE,+B,CAEF,sC,CAEE,yB,CAEF,uC,CAIA,uC,CAFE,4B,CAMF,wC,CAEE,gC,CAGF,uB,CACE,iB,CAIF,oCACE,U,CAEE,gBAIJ,I,CACE,W,CAEF,M,CACE,c,CAGF,S,CACE,W,CAGF,a,CACE,oB,CACA,iB,CACA,W,CACA,Y,CACA,iB,CACA,6B,CACA,iB,CAEF,oBAAoB,c,CAAgB,c,CAClC,yC,CAGF,uB,CACE,c,CACA,iB,CACA,K,CACA,M,CACA,U,CACA,W,CACA,S,CAGF,qB,CACE,Y,CACA,c,CACA,a,CACA,kB,CAGF,oB,CACE,iB,CACA,a,CACA,c,CAGF,yB,CACE,gB","file":"src/css.css","sourcesContent":["/* center the body content horizontally */\r\n/* flow direction is vertical */\r\nbody {\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.hidden {\r\n position: absolute;\r\n width: 0;\r\n height: 0;\r\n pointer-events: none;\r\n opacity: 0;\r\n user-select: none;\r\n overflow: hidden;\r\n}\r\n\r\n/* show the word list in a grid of width 4 */\r\n#word-list {\r\n display: grid;\r\n grid-template-columns: repeat(4, 1fr);\r\n gap: 1rem;\r\n list-style: none;\r\n padding: 0;\r\n margin: 0;\r\n}\r\n\r\n/* if a word is marked found, highlight it */\r\n#word-list>.found {\r\n background-color: var(--color);\r\n}\r\n\r\n#jamo-board {\r\n display: grid;\r\n grid-template-columns: repeat(var(--width), 1fr);\r\n gap: var(--gap);\r\n padding: 0;\r\n margin: 0;\r\n position: relative;\r\n aspect-ratio: 1;\r\n touch-action: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n#jamo-board>i {\r\n text-align: center;\r\n display: inline-block;\r\n width: var(--size);\r\n height: var(--size);\r\n font-size: var(--size);\r\n line-height: 1;\r\n user-select: none;\r\n cursor: pointer;\r\n font-style: normal;\r\n position: relative;\r\n}\r\n\r\n:root[data-mode=\"dark\"] #jamo-board>i::before {\r\n background-color: #fff;\r\n}\r\n:root[data-mode=\"light\"] #jamo-board>i::before {\r\n background-color: #000;\r\n}\r\n:root[data-mode=\"light\"] #jamo-board>.completion-bar::before {\r\n opacity: 0.6;\r\n}\r\n@media (prefers-color-scheme: dark) {\r\n :root[data-mode=\"system\"] #jamo-board>i::before {\r\n background-color: #fff;\r\n }\r\n}\r\n@media (prefers-color-scheme: light) {\r\n :root[data-mode=\"system\"] #jamo-board>i::before {\r\n background-color: #000;\r\n }\r\n :root[data-mode=\"system\"] #jamo-board>.completion-bar::before {\r\n opacity: 0.6;\r\n }\r\n}\r\n\r\n#jamo-board>i::before {\r\n content: '';\r\n display: inline-block;\r\n width: calc(var(--size) * 1.414);\r\n height: calc(var(--size) * 1.414);\r\n top: 50%;\r\n left: 50%;\r\n border-radius: 50%;\r\n pointer-events: none;\r\n opacity: 0;\r\n position: absolute;\r\n transform: translate(-50%, -50%);\r\n z-index: -1;\r\n}\r\n#jamo-board:not(:active)>i:hover::before {\r\n opacity: 0.15;\r\n}\r\n\r\n#jamo-board:active>i {\r\n padding: calc(var(--gap)/4);\r\n margin: calc(var(--gap)/-4);\r\n}\r\n\r\n#jamo-board>.written {\r\n background-color: var(--color);\r\n}\r\n\r\n#jamo-board>.completion-bar {\r\n /* remove from grid flow */\r\n position: absolute;\r\n top: var(--top);\r\n left: var(--left);\r\n width: var(--width);\r\n height: var(--height);\r\n\r\n /* outline: 1px solid red; */\r\n pointer-events: none;\r\n}\r\n\r\n#jamo-board>.completion-bar::before {\r\n\r\n content: '';\r\n position: absolute;\r\n background-color: var(--color); /* Line color */\r\n height:var(--thick); /* Line thickness */\r\n\r\n /* Calculate the bottom-left point of the line */\r\n top: 50%;\r\n left: 50%;\r\n\r\n /* Length of the line using hypot(), assuming --delta-x and --delta-y are defined */\r\n width: var(--hypot);\r\n\r\n /* Rotate line using atan2(), converting result from radians to degrees */\r\n transform: translate(-50%, -50%) rotate(var(--angle));\r\n\r\n /* Ensure the rotation point is the start of the line */\r\n transform-origin: center;\r\n mix-blend-mode: overlay;\r\n opacity: 0.5;\r\n\r\n z-index: -2;\r\n\r\n border-radius: var(--thick);\r\n\r\n /* filter: url(#displacementFilter); */\r\n}\r\n\r\nnav {\r\n height: 2rem;\r\n}\r\n\r\n#dark-mode-toggle {\r\n position: absolute;\r\n z-index: 1;\r\n top: 1rem;\r\n right: 1rem;\r\n width: 2rem;\r\n height: 2rem;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n cursor: pointer;\r\n transform: rotate(45deg);\r\n border-radius: 50%;\r\n overflow: hidden;\r\n padding: 0;\r\n view-transition-name: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.notransition, .notransition>* {\r\n transition: none !important;\r\n}\r\n\r\n#dark-mode-toggle>#sun, #dark-mode-toggle>#moon {\r\n position: absolute;\r\n font-size: 1.5rem;\r\n line-height: 1;\r\n text-align: center;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n transition: clip-path 0.5s;\r\n}\r\n\r\n#dark-mode-toggle>#sun {\r\n color-scheme: light;\r\n background-color: buttonface;\r\n}\r\n\r\n#dark-mode-toggle>#moon {\r\n color-scheme: dark;\r\n background-color: buttonface;\r\n}\r\n\r\n#dark-mode-toggle>#sun::before,\r\n#dark-mode-toggle>#moon::before {\r\n display: inline-block;\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%) rotate(-45deg);\r\n}\r\n\r\n#dark-mode-toggle>#sun::before {\r\n content: 'โ๏ธ';\r\n}\r\n\r\n#dark-mode-toggle>#moon::before {\r\n content: '๐';\r\n}\r\n\r\n:root[data-mode=\"system\"] {\r\n color-scheme: dark light;\r\n}\r\n\r\n:root[data-mode=\"dark\"] {\r\n color-scheme: dark;\r\n}\r\n\r\n:root[data-mode=\"light\"] {\r\n color-scheme: light;\r\n}\r\n\r\n#dark-mode-toggle[data-mode=\"system\"]>#sun {\r\n /* left half of square*/\r\n clip-path: rect(0 50% 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"system\"]>#moon {\r\n /* right half of square */\r\n clip-path: rect(0 100% 100% 50%);\r\n}\r\n#dark-mode-toggle[data-mode=\"dark\"]>#sun {\r\n /* hidden to left side */\r\n clip-path: rect(0 0 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"dark\"]>#moon {\r\n /* fully visible */\r\n clip-path: rect(0 100% 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"light\"]>#sun {\r\n /* fully visible*/\r\n clip-path: rect(0 100% 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"light\"]>#moon {\r\n /* hidden to the right side */\r\n clip-path: rect(0 100% 100% 100%);\r\n}\r\n\r\n#currentJamoCompletions {\r\n position: absolute;\r\n}\r\n\r\n/* side-to-side layout for desktop */\r\n@media screen and (min-width: 720px) {\r\n #word-list {\r\n /* grid-template-columns: repeat(2, 1fr); */\r\n animation: none;\r\n }\r\n}\r\n\r\nmain {\r\n font-size: 0\r\n}\r\nmain>* {\r\n font-size: 1rem;\r\n}\r\n\r\n.noresize {\r\n resize: none;\r\n}\r\n\r\n#file-wrapper {\r\n display: inline-block;\r\n position: relative;\r\n width: 150px; /* Adjust as needed */\r\n height: 100px; /* Adjust as needed */\r\n text-align: center;\r\n border: 1px solid buttonborder;\r\n border-radius: 2px;\r\n}\r\n#file-wrapper:where(:focus-visible, :focus-within) {\r\n outline: -webkit-focus-ring-color auto 1px;\r\n}\r\n\r\n#select-game-state-file {\r\n cursor: pointer;\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n opacity: 0;\r\n}\r\n\r\n#file-wrapper::before {\r\n content: '๐'; /* Folder emoji */\r\n font-size: 50px; /* Adjust emoji size */\r\n display: block;\r\n margin-bottom: 10px; /* Space between emoji and text */\r\n}\r\n\r\n#file-wrapper::after {\r\n content: 'ํ์ผ ๋ถ๋ฌ์ค๊ธฐ'; /* Text below emoji */\r\n display: block;\r\n font-size: 14px; /* Adjust text size */\r\n}\r\n\r\n#settings-panel::backdrop {\r\n background: #0004;\r\n}\r\n"]}
\ No newline at end of file
+{"version":3,"sources":["src/css.css"],"names":[],"mappings":"AAEA,I,CACE,Y,CACA,qB,CACA,sB,CACA,kB,CAGF,O,CACE,iB,CACA,O,CACA,Q,CACA,mB,CACA,S,CACA,gB,CACA,e,CAkBF,W,CAdA,U,CAeE,Y,CAGA,S,CACA,Q,CAnBF,U,CAEE,mC,CACA,Q,CACA,e,CAiFF,oB,CA3EA,iB,CACE,6B,CAGF,W,CAEE,8C,CACA,c,CAGA,iB,CACA,c,CACA,iB,CACA,uC,CAGF,a,CACE,iB,CACA,oB,CACA,iB,CACA,kB,CACA,qB,CACA,a,CACA,gB,CACA,c,CACA,iB,CACA,iB,CAGF,2C,CACE,qB,CAEF,4C,CACE,qB,CAEF,0D,CACE,U,CAEF,mCACE,6C,CACE,uBAGJ,A,oCACE,6C,CACE,qB,CAEF,2D,CACE,YAIJ,qB,CACE,U,CACA,oB,CACA,6B,CACA,8B,CACA,O,CACA,Q,CACA,iB,CACA,mB,CACA,S,CACA,iB,CACA,8B,CACA,U,CAEF,gBAAgB,wB,CACd,W,CAGF,oB,CACE,0B,CACA,0B,CAOF,2B,CAEE,iB,CACA,c,CACA,gB,CACA,kB,CACA,oB,CAGA,mB,CAGF,mC,CAEE,U,CACA,iB,CACA,6B,CACA,mB,CAGA,O,CACA,Q,CAGA,kB,CAGA,mD,CAGA,uB,CACA,sB,CACA,U,CAEA,U,CAEA,0B,CAKF,G,CACE,W,CAGF,iB,CACE,iB,CACA,S,CACA,Q,CACA,U,CACA,U,CACA,W,CACA,Y,CACA,sB,CACA,kB,CACA,c,CACA,uB,CACA,iB,CACA,e,CACA,S,CACA,yB,CACA,uC,CAGF,a,CAAe,e,CACb,yB,CAGsB,uB,CAAxB,sB,CACE,iB,CACA,gB,CACA,a,CACA,iB,CACA,K,CACA,M,CACA,U,CACA,W,CACA,e,CACA,wB,CAIA,kB,CACA,2B,CAGF,uB,CACE,iB,CAKF,+B,CADA,8B,CAEE,oB,CACA,iB,CACA,O,CACA,Q,CACA,6C,CAGF,8B,CACE,Y,CAGF,+B,CACE,Y,CAGF,uB,CACE,uB,CAGF,qB,CACE,iB,CAGF,sB,CACE,kB,CAGF,wC,CAEE,2B,CAEF,yC,CAEE,+B,CAEF,sC,CAEE,yB,CAEF,uC,CAIA,uC,CAFE,4B,CAMF,wC,CAEE,gC,CAGF,uB,CACE,iB,CAIF,oCACE,U,CAEE,gBAIJ,I,CACE,W,CAEF,M,CACE,c,CAGF,S,CACE,W,CAGF,a,CACE,oB,CACA,iB,CACA,W,CACA,Y,CACA,iB,CACA,6B,CACA,iB,CAEF,oBAAoB,c,CAAgB,c,CAClC,yC,CAGF,uB,CACE,c,CACA,iB,CACA,K,CACA,M,CACA,U,CACA,W,CACA,S,CAGF,qB,CACE,Y,CACA,c,CACA,a,CACA,kB,CAGF,oB,CACE,uB,CACA,a,CACA,c,CAGF,yB,CACE,gB","file":"src/css.css","sourcesContent":["/* center the body content horizontally */\r\n/* flow direction is vertical */\r\nbody {\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.hidden {\r\n position: absolute;\r\n width: 0;\r\n height: 0;\r\n pointer-events: none;\r\n opacity: 0;\r\n user-select: none;\r\n overflow: hidden;\r\n}\r\n\r\n/* show the word list in a grid of width 4 */\r\n#word-list {\r\n display: grid;\r\n grid-template-columns: repeat(4, 1fr);\r\n gap: 1rem;\r\n list-style: none;\r\n padding: 0;\r\n margin: 0;\r\n}\r\n\r\n/* if a word is marked found, highlight it */\r\n#word-list>.found {\r\n background-color: var(--color);\r\n}\r\n\r\n#jamo-board {\r\n display: grid;\r\n grid-template-columns: repeat(var(--width), 1fr);\r\n gap: var(--gap);\r\n padding: 0;\r\n margin: 0;\r\n position: relative;\r\n aspect-ratio: 1;\r\n touch-action: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n#jamo-board>i {\r\n text-align: center;\r\n display: inline-block;\r\n width: var(--size);\r\n height: var(--size);\r\n font-size: var(--size);\r\n line-height: 1;\r\n user-select: none;\r\n cursor: pointer;\r\n font-style: normal;\r\n position: relative;\r\n}\r\n\r\n:root[data-mode=\"dark\"] #jamo-board>i::before {\r\n background-color: #fff;\r\n}\r\n:root[data-mode=\"light\"] #jamo-board>i::before {\r\n background-color: #000;\r\n}\r\n:root[data-mode=\"light\"] #jamo-board>.completion-bar::before {\r\n opacity: 0.6;\r\n}\r\n@media (prefers-color-scheme: dark) {\r\n :root[data-mode=\"system\"] #jamo-board>i::before {\r\n background-color: #fff;\r\n }\r\n}\r\n@media (prefers-color-scheme: light) {\r\n :root[data-mode=\"system\"] #jamo-board>i::before {\r\n background-color: #000;\r\n }\r\n :root[data-mode=\"system\"] #jamo-board>.completion-bar::before {\r\n opacity: 0.6;\r\n }\r\n}\r\n\r\n#jamo-board>i::before {\r\n content: '';\r\n display: inline-block;\r\n width: calc(var(--size) * 1.414);\r\n height: calc(var(--size) * 1.414);\r\n top: 50%;\r\n left: 50%;\r\n border-radius: 50%;\r\n pointer-events: none;\r\n opacity: 0;\r\n position: absolute;\r\n transform: translate(-50%, -50%);\r\n z-index: -1;\r\n}\r\n#jamo-board:not(:active)>i:hover::before {\r\n opacity: 0.15;\r\n}\r\n\r\n#jamo-board:active>i {\r\n padding: calc(var(--gap)/4);\r\n margin: calc(var(--gap)/-4);\r\n}\r\n\r\n#jamo-board>.written {\r\n background-color: var(--color);\r\n}\r\n\r\n#jamo-board>.completion-bar {\r\n /* remove from grid flow */\r\n position: absolute;\r\n top: var(--top);\r\n left: var(--left);\r\n width: var(--width);\r\n height: var(--height);\r\n\r\n /* outline: 1px solid red; */\r\n pointer-events: none;\r\n}\r\n\r\n#jamo-board>.completion-bar::before {\r\n\r\n content: '';\r\n position: absolute;\r\n background-color: var(--color); /* Line color */\r\n height:var(--thick); /* Line thickness */\r\n\r\n /* Calculate the bottom-left point of the line */\r\n top: 50%;\r\n left: 50%;\r\n\r\n /* Length of the line using hypot(), assuming --delta-x and --delta-y are defined */\r\n width: var(--hypot);\r\n\r\n /* Rotate line using atan2(), converting result from radians to degrees */\r\n transform: translate(-50%, -50%) rotate(var(--angle));\r\n\r\n /* Ensure the rotation point is the start of the line */\r\n transform-origin: center;\r\n mix-blend-mode: overlay;\r\n opacity: 0.5;\r\n\r\n z-index: -2;\r\n\r\n border-radius: var(--thick);\r\n\r\n /* filter: url(#displacementFilter); */\r\n}\r\n\r\nnav {\r\n height: 2rem;\r\n}\r\n\r\n#dark-mode-toggle {\r\n position: absolute;\r\n z-index: 1;\r\n top: 1rem;\r\n right: 1rem;\r\n width: 2rem;\r\n height: 2rem;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n cursor: pointer;\r\n transform: rotate(45deg);\r\n border-radius: 50%;\r\n overflow: hidden;\r\n padding: 0;\r\n view-transition-name: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.notransition, .notransition>* {\r\n transition: none !important;\r\n}\r\n\r\n#dark-mode-toggle>#sun, #dark-mode-toggle>#moon {\r\n position: absolute;\r\n font-size: 1.5rem;\r\n line-height: 1;\r\n text-align: center;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n transition: clip-path 0.5s;\r\n}\r\n\r\n#dark-mode-toggle>#sun {\r\n color-scheme: light;\r\n background-color: buttonface;\r\n}\r\n\r\n#dark-mode-toggle>#moon {\r\n color-scheme: dark;\r\n background-color: buttonface;\r\n}\r\n\r\n#dark-mode-toggle>#sun::before,\r\n#dark-mode-toggle>#moon::before {\r\n display: inline-block;\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%) rotate(-45deg);\r\n}\r\n\r\n#dark-mode-toggle>#sun::before {\r\n content: 'โ๏ธ';\r\n}\r\n\r\n#dark-mode-toggle>#moon::before {\r\n content: '๐';\r\n}\r\n\r\n:root[data-mode=\"system\"] {\r\n color-scheme: dark light;\r\n}\r\n\r\n:root[data-mode=\"dark\"] {\r\n color-scheme: dark;\r\n}\r\n\r\n:root[data-mode=\"light\"] {\r\n color-scheme: light;\r\n}\r\n\r\n#dark-mode-toggle[data-mode=\"system\"]>#sun {\r\n /* left half of square*/\r\n clip-path: rect(0 50% 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"system\"]>#moon {\r\n /* right half of square */\r\n clip-path: rect(0 100% 100% 50%);\r\n}\r\n#dark-mode-toggle[data-mode=\"dark\"]>#sun {\r\n /* hidden to left side */\r\n clip-path: rect(0 0 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"dark\"]>#moon {\r\n /* fully visible */\r\n clip-path: rect(0 100% 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"light\"]>#sun {\r\n /* fully visible*/\r\n clip-path: rect(0 100% 100% 0);\r\n}\r\n#dark-mode-toggle[data-mode=\"light\"]>#moon {\r\n /* hidden to the right side */\r\n clip-path: rect(0 100% 100% 100%);\r\n}\r\n\r\n#currentJamoCompletions {\r\n position: absolute;\r\n}\r\n\r\n/* side-to-side layout for desktop */\r\n@media screen and (min-width: 720px) {\r\n #word-list {\r\n /* grid-template-columns: repeat(2, 1fr); */\r\n animation: none;\r\n }\r\n}\r\n\r\nmain {\r\n font-size: 0\r\n}\r\nmain>* {\r\n font-size: 1rem;\r\n}\r\n\r\n.noresize {\r\n resize: none;\r\n}\r\n\r\n#file-wrapper {\r\n display: inline-block;\r\n position: relative;\r\n width: 150px; /* Adjust as needed */\r\n height: 100px; /* Adjust as needed */\r\n text-align: center;\r\n border: 1px solid buttonborder;\r\n border-radius: 2px;\r\n}\r\n#file-wrapper:where(:focus-visible, :focus-within) {\r\n outline: -webkit-focus-ring-color auto 1px;\r\n}\r\n\r\n#select-game-state-file {\r\n cursor: pointer;\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n opacity: 0;\r\n}\r\n\r\n#file-wrapper::before {\r\n content: '๐'; /* Folder emoji */\r\n font-size: 50px; /* Adjust emoji size */\r\n display: block;\r\n margin-bottom: 10px; /* Space between emoji and text */\r\n}\r\n\r\n#file-wrapper::after {\r\n content: attr(data-text); /* Text below emoji */\r\n display: block;\r\n font-size: 14px; /* Adjust text size */\r\n}\r\n\r\n#settings-panel::backdrop {\r\n background: #0004;\r\n}\r\n"]}
\ No newline at end of file
diff --git a/index.html b/index.html
index 2e87795..7d35c34 100644
--- a/index.html
+++ b/index.html
@@ -1 +1 @@
-
ํ๊ธ ๋จ์ด ์ฐพ๊ธฐ ๊ฒ์
\ No newline at end of file
+ํ๊ธ ๋จ์ด ์ฐพ๊ธฐ ๊ฒ์
\ No newline at end of file
diff --git a/js.js b/js.js
index 43e03e2..bba2b4f 100644
--- a/js.js
+++ b/js.js
@@ -1,3 +1,3 @@
-function t(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=Array(n);e>>1|(1&t)<<15)^n},0)}function z(){var t=b.join(),n=Array.from({length:144},function(t,n){return T[n].textContent}).join(""),e=Array.from(A.querySelectorAll(".completion-bar")).filter(function(t){return t!==_}).map(function(t){return"".concat(t.dataset.start,",").concat(t.dataset.end)}).join(),r="".concat(2,"|").concat(t,"|").concat(12,"|").concat(12,"|").concat(n,"|").concat(e,"|").concat(y);return"".concat(r,"|").concat(x(r))}function L(t){var r=n(e(t.normalize("NFD")).concat(["","",""]).slice(0,3),3),o=r[0],a=r[1],i=r[2];return["ใฑใฒใดใทใธในใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
"[o.charCodeAt(0)-d.charCodeAt(0)],String.fromCharCode(a.charCodeAt(0)+h),i.length?"ใฑใฒใณใดใตใถใทในใบใปใผใฝใพใฟใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
"[i.charCodeAt(0)-m.charCodeAt(0)]:""].flatMap(function(t){var n;return e(null!==(n=v[t])&&void 0!==n?n:t)})}var I=b.flatMap(function(t){return e(t.normalize("NFC")).flatMap(L)});function N(t,n){n.classList.add("notransition"),t(),requestAnimationFrame(function(){requestAnimationFrame(function(){n.classList.remove("notransition")})})}A.style.setProperty("--gap","".concat(.75,"em")),A.style.setProperty("--width",12),A.style.setProperty("--height",12),N(function(){for(var t=0;t<144;t++){var n=C.content.cloneNode(!0).querySelector("i"),e=M?M[3][t]:D(0,1)?j[D(0,j.length-1)]:I[D(0,I.length-1)];n.textContent=e,A.appendChild(n)}},A);var B=Array.from({length:144},function(){return null}),q=function(t,n,e,o){if(e<0||e>7)throw RangeError("direction must be 0 to 7");for(var a=-1;a<=1;a++)for(var i=-1;i<=1;i++)if(r[a+1][i+1]===e)return[t+i*o,n+a*o]},T=A.querySelectorAll("#jamo-board>i");T.forEach(function(t,n){t.dataset.index=n});var k=function(t,e,r,o){var a=g(t);if(a.length>12&&a.length>12)throw RangeError("word too long for board");for(var i=0;i=12||u<0||u>=12)return!1;var s=B[12*u+l];if(s&&s!==a[i])return!1}for(var d=0;d4&&void 0!==arguments[4]?arguments[4]:null,a=F(O,function(){return document.getElementById("completion-bar-template")}),i=null!=o?o:a.content.cloneNode(!0).querySelector(".completion-bar");i.dataset.start="".concat(t,",").concat(n),i.dataset.end="".concat(e,",").concat(r);var c=Math.sign(r-n),l=Math.min(t,e),u=Math.max(t,e),s=Math.min(n,r),d=Math.max(n,r),f=(u-l+1)*2+.75*(u-l),m=(d-s+1)*2+.75*(d-s);i.style.setProperty("--top","".concat(2*s+.75*s,"em")),i.style.setProperty("--left","".concat(2*l+.75*l,"em")),i.style.setProperty("--width","".concat(f,"em")),i.style.setProperty("--height","".concat(m,"em")),i.style.setProperty("--thick","".concat(2.25,"em")),i.style.setProperty("--hypot","".concat(Math.hypot(f,m)+.25,"em")),i.style.setProperty("--angle","".concat(180*Math.atan2(c*m,Math.sign(e-t)*f)/Math.PI,"deg"));var h=Math.floor(43758.5453*Math.sin(12.9898*t+78.233*n)%1*H)*Math.floor(360/H),v="oklch(75% 75% ".concat(y+h,"deg)");return i.style.setProperty("--color",v),i}function R(t,n){t.style.setProperty("--color",n.style.getPropertyValue("--color")),t.classList.add("found")}function D(t,n){return Math.floor(Math.random()*(n-t+1))+t}A.style.setProperty("--size","".concat(2,"rem"));var H=16;M||(y=D(0,359));var V=Array.from({length:4},function(){return 16}),J=64,X=function(){for(var t=D(0,J-1),n=0,e=0;e<4;e++)if(t<(n+=V[e]))return e};if(M)M[4].forEach(function(t){var e,r,o=n(t,4),a=o[0],i=o[1],c=o[2],l=o[3];e=O(a,i,c,l),r=tr(te(a,i,c,l)),R(S.querySelector('li[data-word="'.concat(r,'"]')),e),A.appendChild(e)});else try{b.toSorted(function(t,n){return g(n).length-g(t).length}).forEach(function(t){for(var n,e,r,o=0;;){n=D(0,11),e=D(0,11);var a=((r=X())+6)%8;if(k(t,n,e,a)){V[r]--,J--,16-V[r]>16/3&&(J-=V[r],V[r]=0);break}if(o>256)throw"The board generation was stuck in impossible state, so the page was reloaded.";o++}})}catch(n){console.error(n),a.forEach(clearInterval),a.length=0,e(document.body.children).filter(function(t){return t!==document.currentScript}).forEach(function(t){return t.remove()}),document.body.innerHTML=c,o=!1,t();return}var Y=document.getElementById("dark-mode-toggle");Y.addEventListener("click",function(){var t,n=Y.dataset.mode,e=Y.dataset.modeOptions.split("|"),r=e[(e.indexOf(n)+1)%e.length];t=function(){Y.dataset.mode=r,document.documentElement.dataset.mode=r},document.startViewTransition?document.startViewTransition(t):t(),localStorage.darkMode=r});var G=localStorage.darkMode;G&&N(function(){Y.dataset.mode=G,document.documentElement.dataset.mode=G},Y);var U=new Proxy({value:!1},{set:function(t,n,e){if("value"===n&&"boolean"==typeof e)return Reflect.set(t,n,e)}}),W=[-1,-1],$=-1,K=[-1,-1];function Q(t){return[t%12,Math.floor(t/12)]}function Z(t,e){var o=n(t,2),a=o[0],i=o[1],c=n(e,2),l=c[0],u=c[1];if(!(a===l||i===u||Math.abs(a-l)===Math.abs(i-u)))return -1;var s=[l-a,u-i],d=s[0];return r[Math.sign(s[1])+1][Math.sign(d)+1]}var _=null;function tt(){if(-1!==W[0]&&-1!==W[1]){var t=null!==_,e=n(W,2),r=e[0],o=e[1],a=n(-1===K[0]&&-1===K[1]?[r,o]:K,2);_=O(r,o,a[0],a[1],_),t||A.appendChild(_)}}function tn(t,n){for(var e=te?t-e:0}A.addEventListener("pointerdown",function(t){var n=document.elementFromPoint(t.clientX,t.clientY);n.matches("#jamo-board>i")&&(U.value=!0,W=Q(1*n.dataset.index),K=[-1,-1],$=-1,tt())}),document.addEventListener("pointerup",function(t){P=2===t.button,U.value=!1,-1!==K[0]&&-1!==K[1]&&(W[0]!==K[0]||W[1]!==K[1])&&function(t){try{if(null===t)return;var e=n(t.dataset.start.split(",").map(Number),2),r=e[0],o=e[1],a=n(t.dataset.end.split(",").map(Number),2),i=a[0],c=a[1];if(r===i&&o===c){t.remove();return}var l=Z([r,o],[i,c]);if(-1===l){t.remove();return}var u=te(r,o,i,c),s=tr(u);if(s){var d=S.querySelector('li[data-word="'.concat(s,'"]'));d&&(d.classList.contains("found")?t.remove():R(d,t))}else t.remove()}finally{_=null}}(_)}),document.addEventListener("pointermove",function(t){if(U.value){t.preventDefault();var e=document.elementFromPoint(t.clientX,t.clientY);if(null==e?void 0:e.matches("#jamo-board>i")){var r=Q(1*e.dataset.index);if(W[0]===r[0]&&W[1]===r[1])return;var o=Z(W,r);if(-1!==o)K=r,$=o;else if(-1!==K[0]&&-1!==K[1]){var a=n((l=W,u=$,d=(s=n(l,2))[0],f=s[1],p=(y=[(h=(m=n(r,2))[0])-d,(v=m[1])-f])[0],g=y[1],M=(b=[h-d,v-f])[0],w=b[1],E=(S=[h-d,v-f])[0],Math.abs(M)=12||c<0||c>=12){var l,u,s,d,f,m,h,v,y,p,g,b,M,w,S,E,A,C,P,j,x,z=Z(W,[i,c]),L=to(i,0,11),I=to(c,0,11);i=(x=n(q(i,c,z,-Math.max(L,I)),2))[0],c=x[1]}K=[i,c]}-1!==K[0]&&-1!==K[1]&&(T[12*K[1]+K[0]],tt())}}}),M||s("gameState",z()),window.serializeGameState=z;var ta=document.querySelector("main"),ti=function(){var t=screen.availWidth,n=screen.availHeight;S.style.zoom=1,S.style.fontSize="1rem",A.style.zoom=1,ta.style.zoom=1;var e=S.getBoundingClientRect().width,r=A.getBoundingClientRect().width;S.style.zoom=Math.min(1,t/e),S.style.fontSize="".concat(1/S.style.zoom,"rem"),A.style.zoom=Math.min(1,t/r);var o=ta.getBoundingClientRect().height;ta.style.zoom=Math.min(1,n/o)};ti(),window.addEventListener("resize",ti),window.addEventListener("beforeunload",function(){u("gameState")&&s("gameState",z())}),performance.now()}(),document.getElementById("show-settings-panel").addEventListener("click",function(){document.getElementById("settings-panel").showModal()});
+function t(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=Array(n);e>>1|(1&t)<<15)^n},0)}function P(){var t=p.join(),n=Array.from({length:144},function(t,n){return N[n].textContent}).join(""),e=Array.from(E.querySelectorAll(".completion-bar")).filter(function(t){return t!==te}).map(function(t){return"".concat(t.dataset.start,",").concat(t.dataset.end)}).join(),r="".concat(2,"|").concat(t,"|").concat(12,"|").concat(12,"|").concat(n,"|").concat(e,"|").concat(v);return"".concat(r,"|").concat(L(r))}function j(t){var r=n(e(t.normalize("NFD")).concat(["","",""]).slice(0,3),3),o=r[0],i=r[1],l=r[2];return["ใฑใฒใดใทใธในใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
"[o.charCodeAt(0)-a.charCodeAt(0)],String.fromCharCode(i.charCodeAt(0)+u),l.length?"ใฑใฒใณใดใตใถใทในใบใปใผใฝใพใฟใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
"[l.charCodeAt(0)-c.charCodeAt(0)]:""].flatMap(function(t){var n;return e(null!==(n=h[t])&&void 0!==n?n:t)})}var z=p.flatMap(function(t){return e(t.normalize("NFC")).flatMap(j)});function I(t,n){n.classList.add("notransition"),t(),requestAnimationFrame(function(){requestAnimationFrame(function(){n.classList.remove("notransition")})})}E.style.setProperty("--gap","".concat(.75,"em")),E.style.setProperty("--width",12),E.style.setProperty("--height",12),I(function(){for(var t=0;t<144;t++){var n=A.content.cloneNode(!0).querySelector("i"),e=M?M[3][t]:J(0,1)?x[J(0,x.length-1)]:z[J(0,z.length-1)];n.textContent=e,E.appendChild(n)}},E);var q=Array.from({length:144},function(){return null}),B=function(n,e,r,o){if(r<0||r>7)throw RangeError("direction must be 0 to 7");for(var a=-1;a<=1;a++)for(var i=-1;i<=1;i++)if(t[a+1][i+1]===r)return[n+i*o,e+a*o]},N=E.querySelectorAll("#jamo-board>i");N.forEach(function(t,n){t.dataset.index=n});var k=function(t,e,r,o){var a=g(t);if(a.length>12&&a.length>12)throw RangeError("word too long for board");for(var i=0;i=12||u<0||u>=12)return!1;var s=q[12*u+l];if(s&&s!==a[i])return!1}for(var d=0;d4&&void 0!==arguments[4]?arguments[4]:null,a=T(F,function(){return document.getElementById("completion-bar-template")}),i=null!=o?o:a.content.cloneNode(!0).querySelector(".completion-bar");i.dataset.start="".concat(t,",").concat(n),i.dataset.end="".concat(e,",").concat(r);var c=Math.sign(r-n),l=Math.min(t,e),u=Math.max(t,e),s=Math.min(n,r),d=Math.max(n,r),f=(u-l+1)*2+.75*(u-l),m=(d-s+1)*2+.75*(d-s);i.style.setProperty("--top","".concat(2*s+.75*s,"em")),i.style.setProperty("--left","".concat(2*l+.75*l,"em")),i.style.setProperty("--width","".concat(f,"em")),i.style.setProperty("--height","".concat(m,"em")),i.style.setProperty("--thick","".concat(2.25,"em")),i.style.setProperty("--hypot","".concat(Math.hypot(f,m)+.25,"em")),i.style.setProperty("--angle","".concat(180*Math.atan2(c*m,Math.sign(e-t)*f)/Math.PI,"deg"));var h=Math.floor(43758.5453*Math.sin(12.9898*t+78.233*n)%1*X)*Math.floor(360/X),y="oklch(75% 75% ".concat(v+h,"deg)");return i.style.setProperty("--color",y),i}E.style.setProperty("--size","".concat(2,"rem"));var O=0,R=document.getElementById("stage-clear-dialog"),D=R.querySelector("#next-stage"),H=R.querySelector("#cancel-next-stage");function V(t,n){O++,t.style.setProperty("--color",n.style.getPropertyValue("--color")),t.classList.add("found"),16===O&&R.showModal()}function J(t,n){return Math.floor(Math.random()*(n-t+1))+t}D.addEventListener("click",function(){f("gameState"),s()}),H.addEventListener("click",function(){R.close()});var X=16;M||(v=J(0,359));var Y=Array.from({length:4},function(){return 16}),G=64,U=function(){for(var t=J(0,G-1),n=0,e=0;e<4;e++)if(t<(n+=Y[e]))return e};if(M)M[4].forEach(function(t){var e,r,o=n(t,4),a=o[0],i=o[1],c=o[2],l=o[3];e=F(a,i,c,l),r=ti(ta(a,i,c,l)),V(w.querySelector('li[data-word="'.concat(r,'"]')),e),E.appendChild(e)});else try{p.toSorted(function(t,n){return g(n).length-g(t).length}).forEach(function(t){for(var n,e,r,o=0;;){n=J(0,11),e=J(0,11);var a=((r=U())+6)%8;if(k(t,n,e,a)){Y[r]--,G--,16-Y[r]>16/3&&(G-=Y[r],Y[r]=0);break}if(o>256)throw"The board generation was stuck in impossible state, so the page was reloaded.";o++}})}catch(t){console.error(t),s();return}var W=document.getElementById("dark-mode-toggle");W.addEventListener("click",function(){var t,n=W.dataset.mode,e=W.dataset.modeOptions.split("|"),r=e[(e.indexOf(n)+1)%e.length];t=function(){W.dataset.mode=r,document.documentElement.dataset.mode=r},document.startViewTransition?document.startViewTransition(t):t(),localStorage.darkMode=r});var $=localStorage.darkMode;$&&I(function(){W.dataset.mode=$,document.documentElement.dataset.mode=$},W);var K=new Proxy({value:!1},{set:function(t,n,e){if("value"===n&&"boolean"==typeof e)return Reflect.set(t,n,e)}}),Q=[-1,-1],Z=-1,_=[-1,-1];function tt(t){return[t%12,Math.floor(t/12)]}function tn(e,r){var o=n(e,2),a=o[0],i=o[1],c=n(r,2),l=c[0],u=c[1];if(!(a===l||i===u||Math.abs(a-l)===Math.abs(i-u)))return -1;var s=[l-a,u-i],d=s[0];return t[Math.sign(s[1])+1][Math.sign(d)+1]}var te=null;function tr(){if(-1!==Q[0]&&-1!==Q[1]){var t=null!==te,e=n(Q,2),r=e[0],o=e[1],a=n(-1===_[0]&&-1===_[1]?[r,o]:_,2);te=F(r,o,a[0],a[1],te),t||E.appendChild(te)}}function to(t,n){for(var e=te?t-e:0}E.addEventListener("pointerdown",function(t){var n=document.elementFromPoint(t.clientX,t.clientY);n.matches("#jamo-board>i")&&(K.value=!0,Q=tt(1*n.dataset.index),_=[-1,-1],Z=-1,tr())}),document.addEventListener("pointerup",function(t){C=2===t.button,K.value=!1,-1!==_[0]&&-1!==_[1]&&(Q[0]!==_[0]||Q[1]!==_[1])&&function(t){try{if(null===t)return;var e=n(t.dataset.start.split(",").map(Number),2),r=e[0],o=e[1],a=n(t.dataset.end.split(",").map(Number),2),i=a[0],c=a[1];if(r===i&&o===c){t.remove();return}var l=tn([r,o],[i,c]);if(-1===l){t.remove();return}var u=ta(r,o,i,c),s=ti(u);if(s){var d=w.querySelector('li[data-word="'.concat(s,'"]'));d&&(d.classList.contains("found")?t.remove():V(d,t))}else t.remove()}finally{te=null}}(te)}),document.addEventListener("pointermove",function(t){if(K.value){t.preventDefault();var e=document.elementFromPoint(t.clientX,t.clientY);if(null==e?void 0:e.matches("#jamo-board>i")){var r=tt(1*e.dataset.index);if(Q[0]===r[0]&&Q[1]===r[1])return;var o=tn(Q,r);if(-1!==o)_=r,Z=o;else if(-1!==_[0]&&-1!==_[1]){var a=n((l=Q,u=Z,d=(s=n(l,2))[0],f=s[1],g=(y=[(h=(m=n(r,2))[0])-d,(v=m[1])-f])[0],p=y[1],b=(M=[h-d,v-f])[0],w=M[1],E=(S=[h-d,v-f])[0],Math.abs(b)=12||c<0||c>=12){var l,u,s,d,f,m,h,v,y,g,p,M,b,w,S,E,A,C,x,L,P,j=tn(Q,[i,c]),z=tc(i,0,11),I=tc(c,0,11);i=(P=n(B(i,c,j,-Math.max(z,I)),2))[0],c=P[1]}_=[i,c]}-1!==_[0]&&-1!==_[1]&&(N[12*_[1]+_[0]],tr())}}}),M||m("gameState",P()),window.serializeGameState=P;var tl=document.querySelector("main"),tu=function(){var t=screen.availWidth,n=screen.availHeight;w.style.zoom=1,w.style.fontSize="1rem",E.style.zoom=1,tl.style.zoom=1;var e=w.getBoundingClientRect().width,r=E.getBoundingClientRect().width;w.style.zoom=Math.min(1,t/e),w.style.fontSize="".concat(1/w.style.zoom,"rem"),E.style.zoom=Math.min(1,t/r);var o=tl.getBoundingClientRect().height;tl.style.zoom=Math.min(1,n/o)};tu(),window.addEventListener("resize",tu),window.addEventListener("beforeunload",function(){d("gameState")&&m("gameState",P())}),performance.now()}function s(){a.forEach(clearInterval),a.length=0,e(document.body.children).filter(function(t){return t!==document.currentScript}).forEach(function(t){return t.remove()}),document.body.innerHTML=c,o=!1,u()}function d(t,n){var e=localStorage[t];return"string"==typeof e?JSON.parse(e):null!=n?n:null}function f(t){delete localStorage[t]}function m(t,n){localStorage[t]=JSON.stringify(n)}performance.now(),u(),document.getElementById("show-settings-panel").addEventListener("click",function(){document.getElementById("settings-panel").showModal()});
//# sourceMappingURL=js.js.map
\ No newline at end of file
diff --git a/js.js.map b/js.js.map
index b168a19..e5a6efc 100644
--- a/js.js.map
+++ b/js.js.map
@@ -1 +1 @@
-{"version":3,"sources":["src/js.js"],"names":[],"mappings":"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","file":"js.js","sourcesContent":["const documentParsed = performance.now()\r\nlet preventInitCall = false\r\nconst DEBUG = false\r\nconst intervals = []\r\nconst setIntervalWithReset = (fn, ms, ...args) => {\r\n const id = setInterval(fn, ms, ...args)\r\n intervals.push(id)\r\n return id\r\n}\r\nconst GAME_VERSION = 2\r\n\r\nconst scriptHTML = document.currentScript.outerHTML\r\nconst initialHTMLWithoutThisScript = document.body.innerHTML.replace(scriptHTML, '')\r\n\r\nlet wordListFull = `์ฌ๊ณผ\r\n๋ฐ๋๋\r\nํฌ๋\r\n๋ธ๊ธฐ\r\n์ค๋ ์ง\r\n์ฒด๋ฆฌ\r\n๋ณต์ญ์\r\n์๋ฐ\r\nํ์ธ์ ํ\r\n๋ฐฐ\r\n๋ ๋ชฌ\r\n๋ผ์ฆ๋ฒ ๋ฆฌ\r\n๋ธ๋ฃจ๋ฒ ๋ฆฌ\r\nํค์\r\n๋ง๊ณ \r\n์ฐธ์ธ\r\n์๋ณด์นด๋\r\n์๋ฅ\r\n์๋ชฝ\r\n๋๋ฆฌ์\r\n์ฝ์ฝ๋\r\n๋ผ์\r\n์๋\r\n๋ฌดํ๊ณผ\r\n๊ฐ\r\n์ด๊ตฌ\r\n์์ถ\r\n์ํ\r\n๋น๊ทผ\r\n๊ฐ์\r\nํ ๋งํ \r\n์ค์ด\r\n์๊ธ์น\r\nํธ๋ฐ\r\n์ฝฉ\r\n์ฅ์์\r\nํํ๋ฆฌ์นด\r\n๋ธ๋ก์ฝ๋ฆฌ\r\n๊ณ ๊ตฌ๋ง\r\n์์คํ๋ผ๊ฑฐ์ค\r\n์๋ฌ๋ฆฌ\r\n์๋ฐฐ์ถ\r\n๊ณ ์ถ\r\n๋ฒ์ฏ\r\n๋ง๋\r\n์๊ฐ\r\n๋นํธ\r\n์ฝ๋ผ๋น\r\n์ํฐ์ดํฌ\r\n๋ฏธ์ญ\r\n๊น\r\nํธ๋ฐ\r\nํผ๋ง\r\n์ฃฝ์\r\n๋ฌด\r\n๊ณ ์ฌ๋ฆฌ\r\n๊ฐ\r\n์ฒญ๊ฒฝ์ฑ\r\n์ผ์ผ\r\n์ทจ๋๋ฌผ\r\n์น์ปค๋ฆฌ\r\n๋ฏธ๋๋ฆฌ\r\n๋๋\r\nํ ๋\r\n๊ทค\r\n๋์ถ\r\nํํ์ผ\r\n๋ณต๋ถ์\r\n์ ์\r\n๋ถ์ถ\r\n๋งค์ค\r\nํธ๋\r\n๊ฐ์ง\r\n๋
ธ๊ฐ`.split('\\n')\r\n\r\nfunction init() {\r\n 'use strict'\r\n if (preventInitCall) {\r\n throw new Error('This function should not be called more than once.')\r\n }\r\n\r\n preventInitCall = true\r\n\r\n \r\n const dirMap = [\r\n [3, 2, 1],\r\n [4, -1, 0],\r\n [5, 6, 7]\r\n ]\r\n\r\n\r\n const [nfdChoBase, nfdJungBase, nfdJongBase] = [...'๊ฐ'.normalize('NFD')]\r\n const simpleJungBase = 'ใ
'\r\n const jungDiff = simpleJungBase.charCodeAt(0) - nfdJungBase.charCodeAt(0)\r\n\r\n\r\n const simpleTocompositeJamoMap = {\r\n 'ใฑใฑ': 'ใฒ',\r\n 'ใฑใ
': 'ใณ',\r\n 'ใดใ
': 'ใต',\r\n 'ใดใ
': 'ใถ',\r\n 'ใทใท': 'ใธ',\r\n 'ในใฑ': 'ใบ',\r\n 'ในใ
': 'ใป',\r\n 'ในใ
': 'ใผ',\r\n 'ในใ
': 'ใฝ',\r\n 'ในใ
': 'ใพ',\r\n 'ในใ
': 'ใฟ',\r\n 'ในใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
กใ
ฃ': 'ใ
ข'\r\n }\r\n\r\n const compositeToSimpleJamoMap = Object.fromEntries(Object.entries(simpleTocompositeJamoMap).map(([simple, composite]) => [composite, simple]))\r\n\r\n let randomHueBase = -1\r\n let previousGameState\r\n try {\r\n previousGameState = deserializeGameState(load('gameState'))\r\n if (previousGameState) {\r\n randomHueBase = previousGameState[5]\r\n }\r\n } catch (e) {\r\n console.error(e)\r\n clear('gameState')\r\n }\r\n\r\n const cloned = [...wordListFull]\r\n\r\n const wordCount = 16\r\n\r\n function simpleJamoBreakdown(word) {\r\n return [...word.normalize('NFC')].flatMap(decomposeIntoSimple)\r\n }\r\n\r\n const wordList = previousGameState ? previousGameState[0] : []\r\n if (!previousGameState) {\r\n for (let i = 0; i < wordCount; i++) {\r\n wordList.push(...cloned.splice(randomInt(0, cloned.length - 1), 1))\r\n }\r\n cloned.length = 0\r\n }\r\n\r\n const wordListElement = document.getElementById('word-list')\r\n const wordListTemplate = document.getElementById('word-template')\r\n wordList.forEach(word => {\r\n const li = wordListTemplate.content.cloneNode(true).querySelector('li')\r\n li.textContent = word\r\n li.dataset.word = word\r\n wordListElement.appendChild(li)\r\n })\r\n\r\n const jamoBoardElement = document.getElementById('jamo-board')\r\n const jamoBoardTemplate = document.getElementById('jamo-template')\r\n\r\n jamoBoardElement.addEventListener('selectstart', (e) => {\r\n e.preventDefault()\r\n })\r\n let isRightClick = false\r\n jamoBoardElement.addEventListener('contextmenu', (e) => {\r\n if (!isRightClick) {\r\n e.preventDefault()\r\n }\r\n isRightClick = false\r\n })\r\n\r\n\r\n const width = 12\r\n const height = 12\r\n\r\n const simpleJamoList = `ใฑใดใทในใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
กใ
ฃ`\r\n\r\n\r\n function calculateChecksum(gs) {\r\n return [...gs].map(c => c.charCodeAt()).reduce((acc, val) => ((acc >>> 1) | ((acc & 1) << 15)) ^ val, 0)\r\n }\r\n\r\n function serializeGameState() {\r\n const words = wordList.join()\r\n const jamoBoard = Array.from({ length: width * height }, (_, i) => jamoElements[i].textContent).join('')\r\n const completions = Array.from(jamoBoardElement.querySelectorAll('.completion-bar')).filter((elem) => elem !== currentJamoCompletion).map(elem => {\r\n return `${elem.dataset.start},${elem.dataset.end}`\r\n }).join()\r\n const gs = `${GAME_VERSION}|${words}|${width}|${height}|${jamoBoard}|${completions}|${randomHueBase}`\r\n return `${gs}|${calculateChecksum(gs)}`\r\n }\r\n\r\n function deserializeGameState(gs) {\r\n if (gs === null) {\r\n return null\r\n }\r\n const [gameVersion, words, width, height, jamoBoard, completions, randomHueBase, checksum] = gs.split('|')\r\n if (gameVersion * 1 !== GAME_VERSION) {\r\n throw new Error('The saved game state is from a different version of the game.')\r\n }\r\n const expectedChecksum = calculateChecksum(`${gameVersion}|${words}|${width}|${height}|${jamoBoard}|${completions}|${randomHueBase}`)\r\n if (expectedChecksum !== checksum * 1) {\r\n throw new Error('saved game state is corrupted')\r\n }\r\n if (jamoBoard.length !== width * height) {\r\n throw new Error('saved game state is corrupted')\r\n }\r\n return [\r\n words.split(','),\r\n width * 1,\r\n height * 1,\r\n jamoBoard,\r\n completions ? completions.split(',').map(Number).reduce((acc, val) => {\r\n if (!acc.length || acc.at(-1).length === 4) {\r\n acc.push([])\r\n }\r\n acc.at(-1).push(val)\r\n return acc\r\n }, []) : [],\r\n randomHueBase * 1\r\n ]\r\n }\r\n\r\n\r\n function decomposeIntoSimple(char) {\r\n const [nfdCho, nfdJung, nfdJong] = [...char.normalize('NFD')].concat(['', '', '']).slice(0, 3)\r\n const simpleCho = `ใฑใฒใดใทใธในใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
`[nfdCho.charCodeAt(0) - nfdChoBase.charCodeAt(0)]\r\n const simpleJung = String.fromCharCode(nfdJung.charCodeAt(0) + jungDiff)\r\n const simpleJong = nfdJong.length ? `ใฑใฒใณใดใตใถใทในใบใปใผใฝใพใฟใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
`[nfdJong.charCodeAt(0) - nfdJongBase.charCodeAt(0)] : ''\r\n return [simpleCho, simpleJung, simpleJong].flatMap(jamo => [...(compositeToSimpleJamoMap[jamo] ?? jamo)])\r\n }\r\n\r\n function composeIntoComposite(simpleJamo) { // @TODO: complete this function\r\n simpleJamo = [...simpleJamo]\r\n const hangulImeStateMachine = {\r\n cho: {\r\n 'ใฑ': ['ใฑ', 'jung'],\r\n 'ใดในใ
ใ
ใ
ใ
ใ
ใ
ใ
': ['jung'],\r\n 'ใท': ['ใท', 'jung'],\r\n 'ใ
': ['ใ
', 'jung'],\r\n 'ใ
': ['ใ
', 'jung'],\r\n 'ใ
': ['ใ
', 'jung'],\r\n },\r\n jung: {\r\n 'ใ
ใ
ใ
ใ
ใ
ก': ['ใ
ฃ', 'jong'],\r\n 'ใ
': ['ใ
', 'ใ
ฃ', 'jong'],\r\n 'ใ
ใ
ใ
ฃ': ['jong'],\r\n 'ใ
': ['ใ
', 'ใ
ฃ', 'jong'],\r\n },\r\n jong: {\r\n 'ใฑ': ['ใฑ', 'ใ
', 'cho'],\r\n 'ใด': ['ใ
', 'ใ
', 'cho'],\r\n 'ใทใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
': ['cho'],\r\n 'ใน': ['ใฑ', 'ใ
', 'ใ
', 'ใ
', 'ใ
', 'ใ
', 'ใ
', 'cho'],\r\n 'ใ
': ['ใ
', 'cho'],\r\n 'ใ
': ['ใ
', 'cho'],\r\n }\r\n }\r\n const maxLengths = {\r\n cho: 2,\r\n jung: 3,\r\n jong: 2\r\n }\r\n let currentLengths = {\r\n cho: 0,\r\n jung: 0,\r\n jong: 0\r\n }\r\n // 'ใ
ใ
ใ
ใ
ใ
ฃในใฑ' -> [['ใ
ใ
'], ['ใ
','ใ
','ใ
ฃ'], ['ใน', 'ใฑ']]\r\n let currentState = 'cho'\r\n const grouped = []\r\n const group = []\r\n let nextCandidate = null\r\n while (simpleJamo[0]) {\r\n const jamo = simpleJamo.shift() // 'ใ
'\r\n group.push(jamo)\r\n currentLengths[currentState]++\r\n const transitionOptions = hangulImeStateMachine[currentState] // cho: { ... }\r\n const transition = Object.entries(transitionOptions).find(([jamoOptions, _]) => jamoOptions.includes(jamo))\r\n if (DEBUG) debugger\r\n if (!transition) {\r\n throw new Error('cannot find suitable continuation for jamo sequence')\r\n }\r\n const [_, targetStates] = transition // ['ใ
', 'jung']\r\n if (targetStates.length === 1 || currentLengths[currentState] === maxLengths[currentState]) {\r\n currentLengths[currentState] = 0\r\n currentState = targetStates[0]\r\n grouped.push([...group])\r\n group.length = 0\r\n if (nextCandidate && !nextCandidate.includes(jamo)) {\r\n throw new Error('next candidate mismatch')\r\n }\r\n nextCandidate = null\r\n } else {\r\n nextCandidate = targetStates.slice(0, -1)\r\n }\r\n }\r\n\r\n return grouped\r\n }\r\n if (DEBUG) {\r\n window.composeIntoComposite = composeIntoComposite\r\n }\r\n\r\n const randomJamo = () => {\r\n return simpleJamoList[randomInt(0, simpleJamoList.length - 1)]\r\n }\r\n\r\n const simpleJamoFromWordList = wordList.flatMap(word => [...word.normalize('NFC')].flatMap(decomposeIntoSimple))\r\n const jamoFromWordlist = () => {\r\n return simpleJamoFromWordList[randomInt(0, simpleJamoFromWordList.length - 1)]\r\n }\r\n\r\n const gap = 0.75\r\n\r\n function noTransitionZone(fn, elem) {\r\n elem.classList.add('notransition')\r\n fn()\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => { // 1 frame skip does not work in some cases\r\n elem.classList.remove('notransition')\r\n })\r\n })\r\n }\r\n\r\n const fillJamoBoard = () => {\r\n jamoBoardElement.style.setProperty('--gap', `${gap}em`)\r\n // set css variable for grid styling\r\n jamoBoardElement.style.setProperty('--width', width)\r\n jamoBoardElement.style.setProperty('--height', height)\r\n\r\n noTransitionZone(() => {\r\n for (let i = 0; i < width * height; i++) {\r\n const jamoElement = jamoBoardTemplate.content.cloneNode(true).querySelector('i')\r\n const jamo = previousGameState ? previousGameState[3][i] : (randomInt(0, 1) ? randomJamo() : jamoFromWordlist())\r\n jamoElement.textContent = jamo\r\n jamoBoardElement.appendChild(jamoElement)\r\n }\r\n }, jamoBoardElement)\r\n }\r\n\r\n fillJamoBoard()\r\n\r\n\r\n const jamoWrittenPositions = Array.from({ length: width * height }, () => null)\r\n\r\n\r\n const getPosition = (x, y, direction, progress) => {\r\n if (direction < 0 || direction > 7) {\r\n throw new RangeError('direction must be 0 to 7')\r\n }\r\n for (let dy = -1; dy <= 1; dy++) {\r\n for (let dx = -1; dx <= 1; dx++) {\r\n if (dirMap[dy + 1][dx + 1] === direction) {\r\n return [x + dx * progress, y + dy * progress]\r\n }\r\n }\r\n }\r\n }\r\n\r\n const jamoElements = jamoBoardElement.querySelectorAll('#jamo-board>i');\r\n\r\n jamoElements.forEach((jamoElement, jamoIndex) => {\r\n jamoElement.dataset.index = jamoIndex\r\n })\r\n\r\n /**\r\n * \r\n * @param {string} word \r\n * @param {number} x \r\n * @param {number} y \r\n * @param {number} direction 0 to 7, starting from towards east(right) 1/8 turn CCW each step\r\n */\r\n const writeWord = (word, x, y, direction) => {\r\n const breakdown = simpleJamoBreakdown(word)\r\n // first check without writing\r\n if (breakdown.length > width && breakdown.length > height) {\r\n throw new RangeError('word too long for board')\r\n }\r\n for (let i = 0; i < breakdown.length; i++) {\r\n const [targetX, targetY] = getPosition(x, y, direction, i)\r\n if (targetX < 0 || targetX >= width || targetY < 0 || targetY >= height) {\r\n return false\r\n }\r\n const jamoIndex = targetY * width + targetX\r\n const existingJamo = jamoWrittenPositions[jamoIndex]\r\n if (existingJamo && existingJamo !== breakdown[i]) {\r\n return false\r\n }\r\n }\r\n\r\n let startX;\r\n let startY;\r\n let endX;\r\n let endY;\r\n for (let i = 0; i < breakdown.length; i++) {\r\n const [targetX, targetY] = getPosition(x, y, direction, i)\r\n if (i === 0) {\r\n [startX, startY] = [targetX, targetY]\r\n }\r\n if (i === breakdown.length - 1) {\r\n [endX, endY] = [targetX, targetY]\r\n }\r\n const jamo = breakdown[i]\r\n const jamoIndex = targetY * width + targetX\r\n jamoWrittenPositions[jamoIndex] = jamo\r\n const jamoElement = jamoElements[jamoIndex]\r\n jamoElement.textContent = jamo\r\n }\r\n\r\n return true\r\n }\r\n\r\n const cellSize = 2\r\n jamoBoardElement.style.setProperty('--size', `${cellSize}rem`)\r\n\r\n function memo(key, compute) {\r\n const cache = memo.cache ?? (memo.cache = new Map())\r\n if (cache.has(key)) {\r\n return cache.get(key)\r\n }\r\n const result = compute()\r\n cache.set(key, result)\r\n return result\r\n }\r\n\r\n function createCompletionBarElement(startX, startY, endX, endY, updateElement = null) {\r\n const completionBarTemplate = memo(createCompletionBarElement, () => document.getElementById('completion-bar-template'))\r\n const completionBarElement = updateElement ?? completionBarTemplate.content.cloneNode(true).querySelector('.completion-bar')\r\n const padding = 0.25\r\n\r\n completionBarElement.dataset.start = `${startX},${startY}`\r\n completionBarElement.dataset.end = `${endX},${endY}`\r\n\r\n const sdx = Math.sign(endX - startX)\r\n const sdy = Math.sign(endY - startY)\r\n const xmin = Math.min(startX, endX);\r\n const xmax = Math.max(startX, endX);\r\n const ymin = Math.min(startY, endY);\r\n const ymax = Math.max(startY, endY);\r\n const top = ymin * cellSize + (gap * ymin);\r\n const left = xmin * cellSize + (gap * xmin);\r\n const width = (xmax - xmin + 1) * cellSize + (gap * (xmax - xmin));\r\n const height = (ymax - ymin + 1) * cellSize + (gap * (ymax - ymin));\r\n\r\n completionBarElement.style.setProperty('--top', `${top}em`)\r\n completionBarElement.style.setProperty('--left', `${left}em`)\r\n completionBarElement.style.setProperty('--width', `${width}em`)\r\n completionBarElement.style.setProperty('--height', `${height}em`)\r\n\r\n const hypot = Math.hypot(width, height) + padding\r\n const angle = Math.atan2(sdy * height, sdx * width) * 180 / Math.PI\r\n completionBarElement.style.setProperty('--thick', `${cellSize + padding}em`)\r\n completionBarElement.style.setProperty('--hypot', `${hypot}em`)\r\n completionBarElement.style.setProperty('--angle', `${angle}deg`)\r\n const rand = Math.floor(randomFromCoords(startX, startY) * colorSteps) * Math.floor(360 / colorSteps)\r\n const color = `oklch(75% 75% ${randomHueBase + rand}deg)`\r\n completionBarElement.style.setProperty('--color', color)\r\n\r\n return completionBarElement\r\n }\r\n\r\n function markWordAsFound(wordElement, completionBarElement) {\r\n wordElement.style.setProperty('--color', completionBarElement.style.getPropertyValue('--color'))\r\n wordElement.classList.add('found')\r\n }\r\n\r\n function markCompletionAsCompleted(startX, startY, endX, endY) {\r\n const completionBarElement = createCompletionBarElement(startX, startY, endX, endY)\r\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\r\n const foundWord = findWordByJamoSequence(jamoSequence)\r\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\r\n markWordAsFound(wordElement, completionBarElement)\r\n jamoBoardElement.appendChild(completionBarElement)\r\n }\r\n\r\n function randomInt(min, max) {\r\n return Math.floor(Math.random() * (max - min + 1)) + min\r\n }\r\n\r\n const colorSteps = 16\r\n if (!previousGameState) {\r\n randomHueBase = randomInt(0, 359)\r\n }\r\n\r\n function randomFromCoords(x, y) {\r\n const dot = x * 12.9898 + y * 78.233\r\n return (Math.sin(dot) * 43758.5453) % 1\r\n }\r\n\r\n const easyDirection = true\r\n\r\n const numDirections = easyDirection ? 4 : 8\r\n const directionsProbabilityDist = Array.from({ length: numDirections }, () => wordCount)\r\n let sum = wordCount * numDirections\r\n\r\n const getDirection = () => {\r\n const randomUniform = randomInt(0, sum - 1)\r\n let temp = 0\r\n for (let i = 0; i < numDirections; i++) {\r\n temp += directionsProbabilityDist[i]\r\n if (randomUniform < temp) {\r\n return i\r\n }\r\n }\r\n }\r\n\r\n if (!previousGameState) {\r\n try {\r\n wordList.toSorted((a, b) => {\r\n return simpleJamoBreakdown(b).length - simpleJamoBreakdown(a).length\r\n }).forEach((word) => {\r\n let x\r\n let y\r\n let direction\r\n let repeated = 0\r\n while (true) {\r\n x = randomInt(0, width - 1)\r\n y = randomInt(0, height - 1)\r\n direction = getDirection()\r\n const directionCorrected = easyDirection ? ((direction + 6) % 8) : direction\r\n const success = writeWord(word, x, y, directionCorrected)\r\n if (success) {\r\n directionsProbabilityDist[direction]--\r\n sum--\r\n if (wordCount - directionsProbabilityDist[direction] > wordCount / 3) {\r\n sum -= directionsProbabilityDist[direction]\r\n directionsProbabilityDist[direction] = 0\r\n }\r\n break\r\n }\r\n if (repeated > wordCount ** 2) {\r\n throw 'The board generation was stuck in impossible state, so the page was reloaded.'\r\n }\r\n repeated++\r\n }\r\n })\r\n } catch (e) {\r\n console.error(e)\r\n reset()\r\n return\r\n }\r\n } else {\r\n previousGameState[4].forEach(([startX, startY, endX, endY]) => {\r\n markCompletionAsCompleted(startX, startY, endX, endY)\r\n })\r\n }\r\n\r\n // add event listener for dark mode toggle\r\n const darkModeToggleButton = document.getElementById('dark-mode-toggle')\r\n\r\n const toggleModes = () => {\r\n const mode = darkModeToggleButton.dataset.mode\r\n const options = darkModeToggleButton.dataset.modeOptions.split('|')\r\n const nextMode = options[(options.indexOf(mode) + 1) % options.length]\r\n const startViewTransition = (change) => document.startViewTransition ? document.startViewTransition(change) : change()\r\n startViewTransition(() => {\r\n darkModeToggleButton.dataset.mode = nextMode\r\n document.documentElement.dataset.mode = nextMode\r\n })\r\n localStorage.darkMode = nextMode\r\n }\r\n darkModeToggleButton.addEventListener('click', toggleModes)\r\n const darkMode = localStorage.darkMode\r\n if (darkMode) {\r\n noTransitionZone(() => {\r\n darkModeToggleButton.dataset.mode = darkMode\r\n document.documentElement.dataset.mode = darkMode\r\n }, darkModeToggleButton)\r\n }\r\n\r\n const pointerdown = new Proxy({ value: false }, {\r\n set: (target, prop, value) => {\r\n if (prop === 'value' && typeof value === 'boolean') {\r\n if (DEBUG) {\r\n Array.from(jamoBoardElement.querySelectorAll('.start, .mid, .end')).forEach(elem => elem.classList.remove('start', 'mid', 'end'))\r\n }\r\n return Reflect.set(target, prop, value)\r\n }\r\n }\r\n })\r\n \r\n let dragStartPos = [-1, -1]\r\n let dragDir = -1\r\n let dragEndPos = [-1, -1]\r\n\r\n function indexToPos(index) {\r\n return [index % width, Math.floor(index / width)]\r\n }\r\n\r\n function isOctilinear(origin, target) {\r\n const [ox, oy] = origin\r\n const [tx, ty] = target\r\n const isOrthogonal = ox === tx || oy === ty\r\n const isDiagonal = Math.abs(ox - tx) === Math.abs(oy - ty)\r\n if (!(isOrthogonal || isDiagonal)) {\r\n return -1\r\n }\r\n const [dx, dy] = [tx - ox, ty - oy]\r\n return dirMap[Math.sign(dy) + 1][Math.sign(dx) + 1]\r\n }\r\n\r\n function getClosestOctilinearPoint(origin, target, dir) {\r\n const [ox, oy] = origin\r\n const [tx, ty] = target\r\n const [dx, dy] = [tx - ox, ty - oy]\r\n let [dx1, dy1] = [tx - ox, ty - oy]\r\n let [dx2, dy2] = [tx - ox, ty - oy]\r\n\r\n {\r\n // orthogonal\r\n if (Math.abs(dx1) < Math.abs(dy1)) {\r\n dx1 = 0\r\n } else {\r\n dy1 = 0\r\n }\r\n }\r\n\r\n {\r\n // diagonal\r\n if ((dx2 + dy2) % 2) {\r\n // parity mismatch adjustment\r\n if (Math.abs(dx2) < Math.abs(dy2)) {\r\n dy2 -= Math.sign(dy2)\r\n } else {\r\n dx2 -= Math.sign(dx2)\r\n }\r\n }\r\n let dist = Math.abs(Math.abs(dx2) - Math.abs(dy2)) / 2\r\n if (Math.abs(dx2) < Math.abs(dy2)) {\r\n dx2 += Math.sign(dx2) * dist\r\n dy2 -= Math.sign(dy2) * dist\r\n } else {\r\n dx2 -= Math.sign(dx2) * dist\r\n dy2 += Math.sign(dy2) * dist\r\n }\r\n }\r\n\r\n const dist1 = Math.abs(dx - dx1) + Math.abs(dy - dy1)\r\n const dist2 = Math.abs(dx - dx2) + Math.abs(dy - dy2)\r\n\r\n if (dist1 === dist2) {\r\n if (dir % 2 === 0) {\r\n return [ox + dx1, oy + dy1]\r\n } else {\r\n return [ox + dx2, oy + dy2]\r\n }\r\n } else {\r\n if (dist1 < dist2) {\r\n return [ox + dx1, oy + dy1]\r\n } else {\r\n return [ox + dx2, oy + dy2]\r\n }\r\n }\r\n }\r\n\r\n let currentJamoCompletion = null\r\n\r\n function updateJamoCompletion() {\r\n if (dragStartPos[0] !== -1 && dragStartPos[1] !== -1) {\r\n const exists = currentJamoCompletion !== null\r\n const [sx, sy] = dragStartPos\r\n const [ex, ey] = dragEndPos[0] === -1 && dragEndPos[1] === -1 ? [sx, sy] : dragEndPos\r\n currentJamoCompletion = createCompletionBarElement(sx, sy, ex, ey, currentJamoCompletion)\r\n if (!exists) {\r\n jamoBoardElement.appendChild(currentJamoCompletion)\r\n }\r\n }\r\n }\r\n\r\n function createRange(start, end) {\r\n const inc = start < end ? 1 : -1;\r\n const result = [];\r\n for (let i = 0; i <= Math.abs(end - start); i += 1) {\r\n result.push(i * inc + start)\r\n }\r\n return result\r\n }\r\n\r\n function completionToJamoSequence(startX, startY, endX, endY) {\r\n const rangeX = createRange(startX, endX)\r\n const rangeY = createRange(startY, endY)\r\n const longer = Math.max(rangeX.length, rangeY.length)\r\n const coords = Array.from({ length: longer }, (_, i) => [rangeX[i] ?? startX, rangeY[i] ?? startY])\r\n return coords.map(([x, y]) => jamoElements[y * width + x].textContent).join('')\r\n }\r\n\r\n function findWordByJamoSequence(jamoSequence) {\r\n return wordList.find(word => {\r\n const [simple, reversed] = memo(word, () => {\r\n const simple = simpleJamoBreakdown(word)\r\n return [simple.join(''), simple.toReversed().join('')]\r\n })\r\n return simple === jamoSequence || reversed === jamoSequence\r\n })\r\n }\r\n\r\n function checkJamoCompletion(jamoCompletionElement) {\r\n try {\r\n if (jamoCompletionElement === null) {\r\n return\r\n }\r\n const [startX, startY] = jamoCompletionElement.dataset.start.split(',').map(Number)\r\n const [endX, endY] = jamoCompletionElement.dataset.end.split(',').map(Number)\r\n if (startX === endX && startY === endY) {\r\n jamoCompletionElement.remove()\r\n return\r\n }\r\n const dir = isOctilinear([startX, startY], [endX, endY])\r\n if (dir === -1) {\r\n jamoCompletionElement.remove()\r\n return\r\n }\r\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\r\n const foundWord = findWordByJamoSequence(jamoSequence)\r\n if (foundWord) {\r\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\r\n if (wordElement) {\r\n if (wordElement.classList.contains('found')) {\r\n jamoCompletionElement.remove()\r\n } else {\r\n markWordAsFound(wordElement, jamoCompletionElement)\r\n }\r\n }\r\n } else {\r\n jamoCompletionElement.remove()\r\n }\r\n } finally {\r\n currentJamoCompletion = null\r\n }\r\n }\r\n\r\n jamoBoardElement.addEventListener('pointerdown', (e) => {\r\n const jamoElement = document.elementFromPoint(e.clientX, e.clientY)\r\n if (jamoElement.matches('#jamo-board>i')) {\r\n pointerdown.value = true\r\n if (DEBUG) {\r\n jamoElement.classList.add('start')\r\n }\r\n dragStartPos = indexToPos(jamoElement.dataset.index * 1)\r\n dragEndPos = [-1, -1]\r\n dragDir = -1\r\n updateJamoCompletion()\r\n }\r\n })\r\n\r\n document.addEventListener('pointerup', (e) => {\r\n isRightClick = e.button === 2\r\n pointerdown.value = false\r\n if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1 && (dragStartPos[0] !== dragEndPos[0] || dragStartPos[1] !== dragEndPos[1])) {\r\n checkJamoCompletion(currentJamoCompletion)\r\n }\r\n })\r\n\r\n function calculateOvershoot(value, min, max) {\r\n if (value < min) {\r\n return min - value\r\n }\r\n if (value > max) {\r\n return value - max\r\n }\r\n return 0\r\n }\r\n\r\n document.addEventListener('pointermove', (e) => {\r\n if (pointerdown.value) {\r\n e.preventDefault()\r\n const jamoElement = document.elementFromPoint(e.clientX, e.clientY)\r\n if (jamoElement?.matches('#jamo-board>i')) {\r\n const pos = indexToPos(jamoElement.dataset.index * 1)\r\n if (dragStartPos[0] === pos[0] && dragStartPos[1] === pos[1]) {\r\n return\r\n }\r\n if (DEBUG) {\r\n jamoBoardElement.querySelector('.mid')?.classList.remove('mid')\r\n jamoElement.classList.add('mid')\r\n }\r\n const dir = isOctilinear(dragStartPos, pos)\r\n if (dir !== -1) {\r\n dragEndPos = pos\r\n dragDir = dir\r\n } else if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\r\n let [cx, cy] = getClosestOctilinearPoint(dragStartPos, pos, dragDir)\r\n if (cx < 0 || cx >= width || cy < 0 || cy >= height) {\r\n const correctedDragDir = isOctilinear(dragStartPos, [cx, cy])\r\n const overshootX = calculateOvershoot(cx, 0, width - 1)\r\n const overshootY = calculateOvershoot(cy, 0, height - 1)\r\n const maxOvershoot = Math.max(overshootX, overshootY);\r\n [cx, cy] = getPosition(cx, cy, correctedDragDir, -maxOvershoot)\r\n }\r\n dragEndPos = [cx, cy]\r\n }\r\n if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\r\n const closestIndex = dragEndPos[1] * width + dragEndPos[0]\r\n const closestElement = jamoElements[closestIndex]\r\n if (DEBUG) {\r\n jamoBoardElement.querySelector('.end')?.classList.remove('end')\r\n closestElement.classList.add('end')\r\n }\r\n updateJamoCompletion()\r\n }\r\n }\r\n }\r\n })\r\n\r\n if (!previousGameState) {\r\n save('gameState', serializeGameState())\r\n }\r\n\r\n window.serializeGameState = serializeGameState\r\n\r\n const mainElement = document.querySelector('main')\r\n\r\n const resizeToFit = () => {\r\n const screenWidth = screen.availWidth\r\n const screenHeight = screen.availHeight\r\n\r\n wordListElement.style.zoom = 1\r\n wordListElement.style.fontSize = '1rem'\r\n jamoBoardElement.style.zoom = 1\r\n mainElement.style.zoom = 1\r\n\r\n const wordListElementWidth = wordListElement.getBoundingClientRect().width\r\n const jamoBoardElementWidth = jamoBoardElement.getBoundingClientRect().width\r\n \r\n wordListElement.style.zoom = Math.min(1, screenWidth / wordListElementWidth)\r\n wordListElement.style.fontSize = `${1 / wordListElement.style.zoom}rem`\r\n jamoBoardElement.style.zoom = Math.min(1, screenWidth / jamoBoardElementWidth)\r\n \r\n const mainElementHeight = mainElement.getBoundingClientRect().height\r\n \r\n mainElement.style.zoom = Math.min(1, screenHeight / mainElementHeight)\r\n }\r\n resizeToFit()\r\n window.addEventListener('resize', resizeToFit)\r\n\r\n window.addEventListener('beforeunload', () => {\r\n const currentStateSaved = load('gameState')\r\n if (currentStateSaved) {\r\n save('gameState', serializeGameState())\r\n }\r\n })\r\n\r\n const gameInitialized = performance.now()\r\n\r\n const PROFILE = false\r\n if (PROFILE) {\r\n requestIdleCallback(() => {\r\n const settled = performance.now()\r\n\r\n const t1 = documentParsed - begin\r\n const t2 = jsParsed - documentParsed\r\n const t3 = gameInitialized - jsParsed\r\n const t4 = settled - gameInitialized\r\n const t5 = settled - begin\r\n \r\n const perfHistory = load('perfHistory', [])\r\n perfHistory.push([t1, t2, t3, t4, t5])\r\n save('perfHistory', perfHistory)\r\n })\r\n }\r\n}\r\n\r\nfunction reset() {\r\n intervals.forEach(clearInterval)\r\n intervals.length = 0\r\n const children = [...document.body.children].filter(elem => elem !== document.currentScript)\r\n children.forEach(child => child.remove())\r\n document.body.innerHTML = initialHTMLWithoutThisScript\r\n preventInitCall = false\r\n init()\r\n}\r\n\r\nfunction load(key, fallback) {\r\n const data = localStorage[key]\r\n if (typeof data === 'string') {\r\n return JSON.parse(data)\r\n }\r\n return fallback ?? null\r\n}\r\n\r\nfunction clear(key) {\r\n delete localStorage[key]\r\n}\r\n\r\nfunction save(key, value) {\r\n localStorage[key] = JSON.stringify(value)\r\n}\r\n\r\nfunction average(arr) {\r\n return arr.reduce((acc, val) => acc + val, 0) / arr.length\r\n}\r\nfunction median(arr) {\r\n const sorted = arr.slice().sort((a, b) => a - b)\r\n const mid = Math.floor(sorted.length / 2)\r\n if (sorted.length % 2 === 0) {\r\n return (sorted[mid - 1] + sorted[mid]) / 2\r\n }\r\n return sorted[mid]\r\n}\r\n\r\nfunction getCurrentFunctionName() {\r\n const currentStackRaw = new Error().stack\r\n const callerLine = currentStackRaw.split('\\n').slice(1)[1]\r\n const caller = callerLine.match(/at (\\w+)/)[1]\r\n return caller\r\n}\r\n\r\nconst jsParsed = performance.now()\r\n\r\ninit()\r\n\r\nfunction registerKonamiCodeHandler() {\r\n const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a', 'Enter']\r\n let konamiCodeIndex = 0\r\n const a = async (e) => {\r\n if (e.key === konamiCode[konamiCodeIndex]) {\r\n konamiCodeIndex++\r\n if (konamiCodeIndex === konamiCode.length) {\r\n showCrazyShit()\r\n window.removeEventListener('keydown', a)\r\n }\r\n } else {\r\n konamiCodeIndex = 0\r\n }\r\n }\r\n window.addEventListener('keydown', a, {passive: true})\r\n}\r\n\r\nfunction showCrazyShit() {\r\n const lunaticText = generateLunaticText()\r\n const lunaticElement = document.createElement('div')\r\n lunaticElement.textContent = lunaticText\r\n document.body.appendChild(lunaticElement)\r\n const slices = createRandomStyleSlices({textLength: lunaticText.length, numSlices: 100, maxLength: 5})\r\n // compute overlapping slices and splice them so that they don't overlap\r\n const sliceGroups = slices.reduce((acc, slice) => {\r\n const last = acc[acc.length - 1]\r\n if (last && last.some(s => s.start < slice.end && s.end > slice.start)) {\r\n last.push(slice)\r\n } else {\r\n acc.push([slice])\r\n }\r\n return acc\r\n }, [])\r\n\r\n}\r\n\r\ndocument.getElementById('show-settings-panel').addEventListener('click', () => {\r\n document.getElementById('settings-panel').showModal()\r\n})\r\n"]}
\ No newline at end of file
+{"version":3,"sources":["src/js.js"],"names":[],"mappings":"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","file":"js.js","sourcesContent":["const documentParsed = performance.now()\r\nlet preventInitCall = false\r\nconst DEBUG = false\r\nconst intervals = []\r\nconst setIntervalWithReset = (fn, ms, ...args) => {\r\n const id = setInterval(fn, ms, ...args)\r\n intervals.push(id)\r\n return id\r\n}\r\nconst GAME_VERSION = 2\r\n\r\nconst scriptHTML = document.currentScript.outerHTML\r\nconst initialHTMLWithoutThisScript = document.body.innerHTML.replace(scriptHTML, '')\r\n\r\nlet wordListFull = `์ฌ๊ณผ\r\n๋ฐ๋๋\r\nํฌ๋\r\n๋ธ๊ธฐ\r\n์ค๋ ์ง\r\n์ฒด๋ฆฌ\r\n๋ณต์ญ์\r\n์๋ฐ\r\nํ์ธ์ ํ\r\n๋ฐฐ\r\n๋ ๋ชฌ\r\n๋ผ์ฆ๋ฒ ๋ฆฌ\r\n๋ธ๋ฃจ๋ฒ ๋ฆฌ\r\nํค์\r\n๋ง๊ณ \r\n์ฐธ์ธ\r\n์๋ณด์นด๋\r\n์๋ฅ\r\n์๋ชฝ\r\n๋๋ฆฌ์\r\n์ฝ์ฝ๋\r\n๋ผ์\r\n์๋\r\n๋ฌดํ๊ณผ\r\n๊ฐ\r\n์ด๊ตฌ\r\n์์ถ\r\n์ํ\r\n๋น๊ทผ\r\n๊ฐ์\r\nํ ๋งํ \r\n์ค์ด\r\n์๊ธ์น\r\nํธ๋ฐ\r\n์ฝฉ\r\n์ฅ์์\r\nํํ๋ฆฌ์นด\r\n๋ธ๋ก์ฝ๋ฆฌ\r\n๊ณ ๊ตฌ๋ง\r\n์์คํ๋ผ๊ฑฐ์ค\r\n์๋ฌ๋ฆฌ\r\n์๋ฐฐ์ถ\r\n๊ณ ์ถ\r\n๋ฒ์ฏ\r\n๋ง๋\r\n์๊ฐ\r\n๋นํธ\r\n์ฝ๋ผ๋น\r\n์ํฐ์ดํฌ\r\n๋ฏธ์ญ\r\n๊น\r\nํธ๋ฐ\r\nํผ๋ง\r\n์ฃฝ์\r\n๋ฌด\r\n๊ณ ์ฌ๋ฆฌ\r\n๊ฐ\r\n์ฒญ๊ฒฝ์ฑ\r\n์ผ์ผ\r\n์ทจ๋๋ฌผ\r\n์น์ปค๋ฆฌ\r\n๋ฏธ๋๋ฆฌ\r\n๋๋\r\nํ ๋\r\n๊ทค\r\n๋์ถ\r\nํํ์ผ\r\n๋ณต๋ถ์\r\n์ ์\r\n๋ถ์ถ\r\n๋งค์ค\r\nํธ๋\r\n๊ฐ์ง\r\n๋
ธ๊ฐ`.split('\\n')\r\n\r\nfunction init() {\r\n 'use strict'\r\n if (preventInitCall) {\r\n throw new Error('This function should not be called more than once.')\r\n }\r\n\r\n preventInitCall = true\r\n\r\n \r\n const dirMap = [\r\n [3, 2, 1],\r\n [4, -1, 0],\r\n [5, 6, 7]\r\n ]\r\n\r\n\r\n const [nfdChoBase, nfdJungBase, nfdJongBase] = [...'๊ฐ'.normalize('NFD')]\r\n const simpleJungBase = 'ใ
'\r\n const jungDiff = simpleJungBase.charCodeAt(0) - nfdJungBase.charCodeAt(0)\r\n\r\n\r\n const simpleTocompositeJamoMap = {\r\n 'ใฑใฑ': 'ใฒ',\r\n 'ใฑใ
': 'ใณ',\r\n 'ใดใ
': 'ใต',\r\n 'ใดใ
': 'ใถ',\r\n 'ใทใท': 'ใธ',\r\n 'ในใฑ': 'ใบ',\r\n 'ในใ
': 'ใป',\r\n 'ในใ
': 'ใผ',\r\n 'ในใ
': 'ใฝ',\r\n 'ในใ
': 'ใพ',\r\n 'ในใ
': 'ใฟ',\r\n 'ในใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
': 'ใ
',\r\n 'ใ
ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
ใ
ฃ': 'ใ
',\r\n 'ใ
กใ
ฃ': 'ใ
ข'\r\n }\r\n\r\n const compositeToSimpleJamoMap = Object.fromEntries(Object.entries(simpleTocompositeJamoMap).map(([simple, composite]) => [composite, simple]))\r\n\r\n let randomHueBase = -1\r\n let previousGameState\r\n try {\r\n previousGameState = deserializeGameState(load('gameState'))\r\n if (previousGameState) {\r\n randomHueBase = previousGameState[5]\r\n }\r\n } catch (e) {\r\n console.error(e)\r\n clear('gameState')\r\n }\r\n\r\n const cloned = [...wordListFull]\r\n\r\n const wordCount = 16\r\n\r\n function simpleJamoBreakdown(word) {\r\n return [...word.normalize('NFC')].flatMap(decomposeIntoSimple)\r\n }\r\n\r\n const wordList = previousGameState ? previousGameState[0] : []\r\n if (!previousGameState) {\r\n for (let i = 0; i < wordCount; i++) {\r\n wordList.push(...cloned.splice(randomInt(0, cloned.length - 1), 1))\r\n }\r\n cloned.length = 0\r\n }\r\n\r\n const wordListElement = document.getElementById('word-list')\r\n const wordListTemplate = document.getElementById('word-template')\r\n wordList.forEach(word => {\r\n const li = wordListTemplate.content.cloneNode(true).querySelector('li')\r\n li.textContent = word\r\n li.dataset.word = word\r\n wordListElement.appendChild(li)\r\n })\r\n\r\n const jamoBoardElement = document.getElementById('jamo-board')\r\n const jamoBoardTemplate = document.getElementById('jamo-template')\r\n\r\n jamoBoardElement.addEventListener('selectstart', (e) => {\r\n e.preventDefault()\r\n })\r\n let isRightClick = false\r\n jamoBoardElement.addEventListener('contextmenu', (e) => {\r\n if (!isRightClick) {\r\n e.preventDefault()\r\n }\r\n isRightClick = false\r\n })\r\n\r\n\r\n const width = 12\r\n const height = 12\r\n\r\n const simpleJamoList = `ใฑใดใทในใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
กใ
ฃ`\r\n\r\n\r\n function calculateChecksum(gs) {\r\n return [...gs].map(c => c.charCodeAt()).reduce((acc, val) => ((acc >>> 1) | ((acc & 1) << 15)) ^ val, 0)\r\n }\r\n\r\n function serializeGameState() {\r\n const words = wordList.join()\r\n const jamoBoard = Array.from({ length: width * height }, (_, i) => jamoElements[i].textContent).join('')\r\n const completions = Array.from(jamoBoardElement.querySelectorAll('.completion-bar')).filter((elem) => elem !== currentJamoCompletion).map(elem => {\r\n return `${elem.dataset.start},${elem.dataset.end}`\r\n }).join()\r\n const gs = `${GAME_VERSION}|${words}|${width}|${height}|${jamoBoard}|${completions}|${randomHueBase}`\r\n return `${gs}|${calculateChecksum(gs)}`\r\n }\r\n\r\n function deserializeGameState(gs) {\r\n if (gs === null) {\r\n return null\r\n }\r\n const [gameVersion, words, width, height, jamoBoard, completions, randomHueBase, checksum] = gs.split('|')\r\n if (gameVersion * 1 !== GAME_VERSION) {\r\n throw new Error('The saved game state is from a different version of the game.')\r\n }\r\n const expectedChecksum = calculateChecksum(`${gameVersion}|${words}|${width}|${height}|${jamoBoard}|${completions}|${randomHueBase}`)\r\n if (expectedChecksum !== checksum * 1) {\r\n throw new Error('saved game state is corrupted')\r\n }\r\n if (jamoBoard.length !== width * height) {\r\n throw new Error('saved game state is corrupted')\r\n }\r\n return [\r\n words.split(','),\r\n width * 1,\r\n height * 1,\r\n jamoBoard,\r\n completions ? completions.split(',').map(Number).reduce((acc, val) => {\r\n if (!acc.length || acc.at(-1).length === 4) {\r\n acc.push([])\r\n }\r\n acc.at(-1).push(val)\r\n return acc\r\n }, []) : [],\r\n randomHueBase * 1\r\n ]\r\n }\r\n\r\n\r\n function decomposeIntoSimple(char) {\r\n const [nfdCho, nfdJung, nfdJong] = [...char.normalize('NFD')].concat(['', '', '']).slice(0, 3)\r\n const simpleCho = `ใฑใฒใดใทใธในใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
`[nfdCho.charCodeAt(0) - nfdChoBase.charCodeAt(0)]\r\n const simpleJung = String.fromCharCode(nfdJung.charCodeAt(0) + jungDiff)\r\n const simpleJong = nfdJong.length ? `ใฑใฒใณใดใตใถใทในใบใปใผใฝใพใฟใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
`[nfdJong.charCodeAt(0) - nfdJongBase.charCodeAt(0)] : ''\r\n return [simpleCho, simpleJung, simpleJong].flatMap(jamo => [...(compositeToSimpleJamoMap[jamo] ?? jamo)])\r\n }\r\n\r\n function composeIntoComposite(simpleJamo) { // @TODO: complete this function\r\n simpleJamo = [...simpleJamo]\r\n const hangulImeStateMachine = {\r\n cho: {\r\n 'ใฑ': ['ใฑ', 'jung'],\r\n 'ใดในใ
ใ
ใ
ใ
ใ
ใ
ใ
': ['jung'],\r\n 'ใท': ['ใท', 'jung'],\r\n 'ใ
': ['ใ
', 'jung'],\r\n 'ใ
': ['ใ
', 'jung'],\r\n 'ใ
': ['ใ
', 'jung'],\r\n },\r\n jung: {\r\n 'ใ
ใ
ใ
ใ
ใ
ก': ['ใ
ฃ', 'jong'],\r\n 'ใ
': ['ใ
', 'ใ
ฃ', 'jong'],\r\n 'ใ
ใ
ใ
ฃ': ['jong'],\r\n 'ใ
': ['ใ
', 'ใ
ฃ', 'jong'],\r\n },\r\n jong: {\r\n 'ใฑ': ['ใฑ', 'ใ
', 'cho'],\r\n 'ใด': ['ใ
', 'ใ
', 'cho'],\r\n 'ใทใ
ใ
ใ
ใ
ใ
ใ
ใ
ใ
': ['cho'],\r\n 'ใน': ['ใฑ', 'ใ
', 'ใ
', 'ใ
', 'ใ
', 'ใ
', 'ใ
', 'cho'],\r\n 'ใ
': ['ใ
', 'cho'],\r\n 'ใ
': ['ใ
', 'cho'],\r\n }\r\n }\r\n const maxLengths = {\r\n cho: 2,\r\n jung: 3,\r\n jong: 2\r\n }\r\n let currentLengths = {\r\n cho: 0,\r\n jung: 0,\r\n jong: 0\r\n }\r\n // 'ใ
ใ
ใ
ใ
ใ
ฃในใฑ' -> [['ใ
ใ
'], ['ใ
','ใ
','ใ
ฃ'], ['ใน', 'ใฑ']]\r\n let currentState = 'cho'\r\n const grouped = []\r\n const group = []\r\n let nextCandidate = null\r\n while (simpleJamo[0]) {\r\n const jamo = simpleJamo.shift() // 'ใ
'\r\n group.push(jamo)\r\n currentLengths[currentState]++\r\n const transitionOptions = hangulImeStateMachine[currentState] // cho: { ... }\r\n const transition = Object.entries(transitionOptions).find(([jamoOptions, _]) => jamoOptions.includes(jamo))\r\n if (DEBUG) debugger\r\n if (!transition) {\r\n throw new Error('cannot find suitable continuation for jamo sequence')\r\n }\r\n const [_, targetStates] = transition // ['ใ
', 'jung']\r\n if (targetStates.length === 1 || currentLengths[currentState] === maxLengths[currentState]) {\r\n currentLengths[currentState] = 0\r\n currentState = targetStates[0]\r\n grouped.push([...group])\r\n group.length = 0\r\n if (nextCandidate && !nextCandidate.includes(jamo)) {\r\n throw new Error('next candidate mismatch')\r\n }\r\n nextCandidate = null\r\n } else {\r\n nextCandidate = targetStates.slice(0, -1)\r\n }\r\n }\r\n\r\n return grouped\r\n }\r\n if (DEBUG) {\r\n window.composeIntoComposite = composeIntoComposite\r\n }\r\n\r\n const randomJamo = () => {\r\n return simpleJamoList[randomInt(0, simpleJamoList.length - 1)]\r\n }\r\n\r\n const simpleJamoFromWordList = wordList.flatMap(word => [...word.normalize('NFC')].flatMap(decomposeIntoSimple))\r\n const jamoFromWordlist = () => {\r\n return simpleJamoFromWordList[randomInt(0, simpleJamoFromWordList.length - 1)]\r\n }\r\n\r\n const gap = 0.75\r\n\r\n function noTransitionZone(fn, elem) {\r\n elem.classList.add('notransition')\r\n fn()\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => { // 1 frame skip does not work in some cases\r\n elem.classList.remove('notransition')\r\n })\r\n })\r\n }\r\n\r\n const fillJamoBoard = () => {\r\n jamoBoardElement.style.setProperty('--gap', `${gap}em`)\r\n // set css variable for grid styling\r\n jamoBoardElement.style.setProperty('--width', width)\r\n jamoBoardElement.style.setProperty('--height', height)\r\n\r\n noTransitionZone(() => {\r\n for (let i = 0; i < width * height; i++) {\r\n const jamoElement = jamoBoardTemplate.content.cloneNode(true).querySelector('i')\r\n const jamo = previousGameState ? previousGameState[3][i] : (randomInt(0, 1) ? randomJamo() : jamoFromWordlist())\r\n jamoElement.textContent = jamo\r\n jamoBoardElement.appendChild(jamoElement)\r\n }\r\n }, jamoBoardElement)\r\n }\r\n\r\n fillJamoBoard()\r\n\r\n\r\n const jamoWrittenPositions = Array.from({ length: width * height }, () => null)\r\n\r\n\r\n const getPosition = (x, y, direction, progress) => {\r\n if (direction < 0 || direction > 7) {\r\n throw new RangeError('direction must be 0 to 7')\r\n }\r\n for (let dy = -1; dy <= 1; dy++) {\r\n for (let dx = -1; dx <= 1; dx++) {\r\n if (dirMap[dy + 1][dx + 1] === direction) {\r\n return [x + dx * progress, y + dy * progress]\r\n }\r\n }\r\n }\r\n }\r\n\r\n const jamoElements = jamoBoardElement.querySelectorAll('#jamo-board>i');\r\n\r\n jamoElements.forEach((jamoElement, jamoIndex) => {\r\n jamoElement.dataset.index = jamoIndex\r\n })\r\n\r\n /**\r\n * \r\n * @param {string} word \r\n * @param {number} x \r\n * @param {number} y \r\n * @param {number} direction 0 to 7, starting from towards east(right) 1/8 turn CCW each step\r\n */\r\n const writeWord = (word, x, y, direction) => {\r\n const breakdown = simpleJamoBreakdown(word)\r\n // first check without writing\r\n if (breakdown.length > width && breakdown.length > height) {\r\n throw new RangeError('word too long for board')\r\n }\r\n for (let i = 0; i < breakdown.length; i++) {\r\n const [targetX, targetY] = getPosition(x, y, direction, i)\r\n if (targetX < 0 || targetX >= width || targetY < 0 || targetY >= height) {\r\n return false\r\n }\r\n const jamoIndex = targetY * width + targetX\r\n const existingJamo = jamoWrittenPositions[jamoIndex]\r\n if (existingJamo && existingJamo !== breakdown[i]) {\r\n return false\r\n }\r\n }\r\n\r\n let startX;\r\n let startY;\r\n let endX;\r\n let endY;\r\n for (let i = 0; i < breakdown.length; i++) {\r\n const [targetX, targetY] = getPosition(x, y, direction, i)\r\n if (i === 0) {\r\n [startX, startY] = [targetX, targetY]\r\n }\r\n if (i === breakdown.length - 1) {\r\n [endX, endY] = [targetX, targetY]\r\n }\r\n const jamo = breakdown[i]\r\n const jamoIndex = targetY * width + targetX\r\n jamoWrittenPositions[jamoIndex] = jamo\r\n const jamoElement = jamoElements[jamoIndex]\r\n jamoElement.textContent = jamo\r\n }\r\n\r\n return true\r\n }\r\n\r\n const cellSize = 2\r\n jamoBoardElement.style.setProperty('--size', `${cellSize}rem`)\r\n\r\n function memo(key, compute) {\r\n const cache = memo.cache ?? (memo.cache = new Map())\r\n if (cache.has(key)) {\r\n return cache.get(key)\r\n }\r\n const result = compute()\r\n cache.set(key, result)\r\n return result\r\n }\r\n\r\n function createCompletionBarElement(startX, startY, endX, endY, updateElement = null) {\r\n const completionBarTemplate = memo(createCompletionBarElement, () => document.getElementById('completion-bar-template'))\r\n const completionBarElement = updateElement ?? completionBarTemplate.content.cloneNode(true).querySelector('.completion-bar')\r\n const padding = 0.25\r\n\r\n completionBarElement.dataset.start = `${startX},${startY}`\r\n completionBarElement.dataset.end = `${endX},${endY}`\r\n\r\n const sdx = Math.sign(endX - startX)\r\n const sdy = Math.sign(endY - startY)\r\n const xmin = Math.min(startX, endX);\r\n const xmax = Math.max(startX, endX);\r\n const ymin = Math.min(startY, endY);\r\n const ymax = Math.max(startY, endY);\r\n const top = ymin * cellSize + (gap * ymin);\r\n const left = xmin * cellSize + (gap * xmin);\r\n const width = (xmax - xmin + 1) * cellSize + (gap * (xmax - xmin));\r\n const height = (ymax - ymin + 1) * cellSize + (gap * (ymax - ymin));\r\n\r\n completionBarElement.style.setProperty('--top', `${top}em`)\r\n completionBarElement.style.setProperty('--left', `${left}em`)\r\n completionBarElement.style.setProperty('--width', `${width}em`)\r\n completionBarElement.style.setProperty('--height', `${height}em`)\r\n\r\n const hypot = Math.hypot(width, height) + padding\r\n const angle = Math.atan2(sdy * height, sdx * width) * 180 / Math.PI\r\n completionBarElement.style.setProperty('--thick', `${cellSize + padding}em`)\r\n completionBarElement.style.setProperty('--hypot', `${hypot}em`)\r\n completionBarElement.style.setProperty('--angle', `${angle}deg`)\r\n const rand = Math.floor(randomFromCoords(startX, startY) * colorSteps) * Math.floor(360 / colorSteps)\r\n const color = `oklch(75% 75% ${randomHueBase + rand}deg)`\r\n completionBarElement.style.setProperty('--color', color)\r\n\r\n return completionBarElement\r\n }\r\n\r\n let foundWords = 0\r\n\r\n const stageClearDialog = document.getElementById('stage-clear-dialog')\r\n const yesButton = stageClearDialog.querySelector('#next-stage')\r\n const noButton = stageClearDialog.querySelector('#cancel-next-stage')\r\n yesButton.addEventListener('click', () => {\r\n clear('gameState')\r\n reset()\r\n })\r\n noButton.addEventListener('click', () => {\r\n stageClearDialog.close()\r\n })\r\n\r\n function markWordAsFound(wordElement, completionBarElement) {\r\n foundWords++\r\n wordElement.style.setProperty('--color', completionBarElement.style.getPropertyValue('--color'))\r\n wordElement.classList.add('found')\r\n if (foundWords === wordCount) {\r\n stageClearDialog.showModal()\r\n }\r\n }\r\n\r\n function markCompletionAsCompleted(startX, startY, endX, endY) {\r\n const completionBarElement = createCompletionBarElement(startX, startY, endX, endY)\r\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\r\n const foundWord = findWordByJamoSequence(jamoSequence)\r\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\r\n markWordAsFound(wordElement, completionBarElement)\r\n jamoBoardElement.appendChild(completionBarElement)\r\n }\r\n\r\n function randomInt(min, max) {\r\n return Math.floor(Math.random() * (max - min + 1)) + min\r\n }\r\n\r\n const colorSteps = 16\r\n if (!previousGameState) {\r\n randomHueBase = randomInt(0, 359)\r\n }\r\n\r\n function randomFromCoords(x, y) {\r\n const dot = x * 12.9898 + y * 78.233\r\n return (Math.sin(dot) * 43758.5453) % 1\r\n }\r\n\r\n const easyDirection = true\r\n\r\n const numDirections = easyDirection ? 4 : 8\r\n const directionsProbabilityDist = Array.from({ length: numDirections }, () => wordCount)\r\n let sum = wordCount * numDirections\r\n\r\n const getDirection = () => {\r\n const randomUniform = randomInt(0, sum - 1)\r\n let temp = 0\r\n for (let i = 0; i < numDirections; i++) {\r\n temp += directionsProbabilityDist[i]\r\n if (randomUniform < temp) {\r\n return i\r\n }\r\n }\r\n }\r\n\r\n if (!previousGameState) {\r\n try {\r\n wordList.toSorted((a, b) => {\r\n return simpleJamoBreakdown(b).length - simpleJamoBreakdown(a).length\r\n }).forEach((word) => {\r\n let x\r\n let y\r\n let direction\r\n let repeated = 0\r\n while (true) {\r\n x = randomInt(0, width - 1)\r\n y = randomInt(0, height - 1)\r\n direction = getDirection()\r\n const directionCorrected = easyDirection ? ((direction + 6) % 8) : direction\r\n const success = writeWord(word, x, y, directionCorrected)\r\n if (success) {\r\n directionsProbabilityDist[direction]--\r\n sum--\r\n if (wordCount - directionsProbabilityDist[direction] > wordCount / 3) {\r\n sum -= directionsProbabilityDist[direction]\r\n directionsProbabilityDist[direction] = 0\r\n }\r\n break\r\n }\r\n if (repeated > wordCount ** 2) {\r\n throw 'The board generation was stuck in impossible state, so the page was reloaded.'\r\n }\r\n repeated++\r\n }\r\n })\r\n } catch (e) {\r\n console.error(e)\r\n reset()\r\n return\r\n }\r\n } else {\r\n previousGameState[4].forEach(([startX, startY, endX, endY]) => {\r\n markCompletionAsCompleted(startX, startY, endX, endY)\r\n })\r\n }\r\n\r\n // add event listener for dark mode toggle\r\n const darkModeToggleButton = document.getElementById('dark-mode-toggle')\r\n\r\n const toggleModes = () => {\r\n const mode = darkModeToggleButton.dataset.mode\r\n const options = darkModeToggleButton.dataset.modeOptions.split('|')\r\n const nextMode = options[(options.indexOf(mode) + 1) % options.length]\r\n const startViewTransition = (change) => document.startViewTransition ? document.startViewTransition(change) : change()\r\n startViewTransition(() => {\r\n darkModeToggleButton.dataset.mode = nextMode\r\n document.documentElement.dataset.mode = nextMode\r\n })\r\n localStorage.darkMode = nextMode\r\n }\r\n darkModeToggleButton.addEventListener('click', toggleModes)\r\n const darkMode = localStorage.darkMode\r\n if (darkMode) {\r\n noTransitionZone(() => {\r\n darkModeToggleButton.dataset.mode = darkMode\r\n document.documentElement.dataset.mode = darkMode\r\n }, darkModeToggleButton)\r\n }\r\n\r\n const pointerdown = new Proxy({ value: false }, {\r\n set: (target, prop, value) => {\r\n if (prop === 'value' && typeof value === 'boolean') {\r\n if (DEBUG) {\r\n Array.from(jamoBoardElement.querySelectorAll('.start, .mid, .end')).forEach(elem => elem.classList.remove('start', 'mid', 'end'))\r\n }\r\n return Reflect.set(target, prop, value)\r\n }\r\n }\r\n })\r\n \r\n let dragStartPos = [-1, -1]\r\n let dragDir = -1\r\n let dragEndPos = [-1, -1]\r\n\r\n function indexToPos(index) {\r\n return [index % width, Math.floor(index / width)]\r\n }\r\n\r\n function isOctilinear(origin, target) {\r\n const [ox, oy] = origin\r\n const [tx, ty] = target\r\n const isOrthogonal = ox === tx || oy === ty\r\n const isDiagonal = Math.abs(ox - tx) === Math.abs(oy - ty)\r\n if (!(isOrthogonal || isDiagonal)) {\r\n return -1\r\n }\r\n const [dx, dy] = [tx - ox, ty - oy]\r\n return dirMap[Math.sign(dy) + 1][Math.sign(dx) + 1]\r\n }\r\n\r\n function getClosestOctilinearPoint(origin, target, dir) {\r\n const [ox, oy] = origin\r\n const [tx, ty] = target\r\n const [dx, dy] = [tx - ox, ty - oy]\r\n let [dx1, dy1] = [tx - ox, ty - oy]\r\n let [dx2, dy2] = [tx - ox, ty - oy]\r\n\r\n {\r\n // orthogonal\r\n if (Math.abs(dx1) < Math.abs(dy1)) {\r\n dx1 = 0\r\n } else {\r\n dy1 = 0\r\n }\r\n }\r\n\r\n {\r\n // diagonal\r\n if ((dx2 + dy2) % 2) {\r\n // parity mismatch adjustment\r\n if (Math.abs(dx2) < Math.abs(dy2)) {\r\n dy2 -= Math.sign(dy2)\r\n } else {\r\n dx2 -= Math.sign(dx2)\r\n }\r\n }\r\n let dist = Math.abs(Math.abs(dx2) - Math.abs(dy2)) / 2\r\n if (Math.abs(dx2) < Math.abs(dy2)) {\r\n dx2 += Math.sign(dx2) * dist\r\n dy2 -= Math.sign(dy2) * dist\r\n } else {\r\n dx2 -= Math.sign(dx2) * dist\r\n dy2 += Math.sign(dy2) * dist\r\n }\r\n }\r\n\r\n const dist1 = Math.abs(dx - dx1) + Math.abs(dy - dy1)\r\n const dist2 = Math.abs(dx - dx2) + Math.abs(dy - dy2)\r\n\r\n if (dist1 === dist2) {\r\n if (dir % 2 === 0) {\r\n return [ox + dx1, oy + dy1]\r\n } else {\r\n return [ox + dx2, oy + dy2]\r\n }\r\n } else {\r\n if (dist1 < dist2) {\r\n return [ox + dx1, oy + dy1]\r\n } else {\r\n return [ox + dx2, oy + dy2]\r\n }\r\n }\r\n }\r\n\r\n let currentJamoCompletion = null\r\n\r\n function updateJamoCompletion() {\r\n if (dragStartPos[0] !== -1 && dragStartPos[1] !== -1) {\r\n const exists = currentJamoCompletion !== null\r\n const [sx, sy] = dragStartPos\r\n const [ex, ey] = dragEndPos[0] === -1 && dragEndPos[1] === -1 ? [sx, sy] : dragEndPos\r\n currentJamoCompletion = createCompletionBarElement(sx, sy, ex, ey, currentJamoCompletion)\r\n if (!exists) {\r\n jamoBoardElement.appendChild(currentJamoCompletion)\r\n }\r\n }\r\n }\r\n\r\n function createRange(start, end) {\r\n const inc = start < end ? 1 : -1;\r\n const result = [];\r\n for (let i = 0; i <= Math.abs(end - start); i += 1) {\r\n result.push(i * inc + start)\r\n }\r\n return result\r\n }\r\n\r\n function completionToJamoSequence(startX, startY, endX, endY) {\r\n const rangeX = createRange(startX, endX)\r\n const rangeY = createRange(startY, endY)\r\n const longer = Math.max(rangeX.length, rangeY.length)\r\n const coords = Array.from({ length: longer }, (_, i) => [rangeX[i] ?? startX, rangeY[i] ?? startY])\r\n return coords.map(([x, y]) => jamoElements[y * width + x].textContent).join('')\r\n }\r\n\r\n function findWordByJamoSequence(jamoSequence) {\r\n return wordList.find(word => {\r\n const [simple, reversed] = memo(word, () => {\r\n const simple = simpleJamoBreakdown(word)\r\n return [simple.join(''), simple.toReversed().join('')]\r\n })\r\n return simple === jamoSequence || reversed === jamoSequence\r\n })\r\n }\r\n\r\n function checkJamoCompletion(jamoCompletionElement) {\r\n try {\r\n if (jamoCompletionElement === null) {\r\n return\r\n }\r\n const [startX, startY] = jamoCompletionElement.dataset.start.split(',').map(Number)\r\n const [endX, endY] = jamoCompletionElement.dataset.end.split(',').map(Number)\r\n if (startX === endX && startY === endY) {\r\n jamoCompletionElement.remove()\r\n return\r\n }\r\n const dir = isOctilinear([startX, startY], [endX, endY])\r\n if (dir === -1) {\r\n jamoCompletionElement.remove()\r\n return\r\n }\r\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\r\n const foundWord = findWordByJamoSequence(jamoSequence)\r\n if (foundWord) {\r\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\r\n if (wordElement) {\r\n if (wordElement.classList.contains('found')) {\r\n jamoCompletionElement.remove()\r\n } else {\r\n markWordAsFound(wordElement, jamoCompletionElement)\r\n }\r\n }\r\n } else {\r\n jamoCompletionElement.remove()\r\n }\r\n } finally {\r\n currentJamoCompletion = null\r\n }\r\n }\r\n\r\n jamoBoardElement.addEventListener('pointerdown', (e) => {\r\n const jamoElement = document.elementFromPoint(e.clientX, e.clientY)\r\n if (jamoElement.matches('#jamo-board>i')) {\r\n pointerdown.value = true\r\n if (DEBUG) {\r\n jamoElement.classList.add('start')\r\n }\r\n dragStartPos = indexToPos(jamoElement.dataset.index * 1)\r\n dragEndPos = [-1, -1]\r\n dragDir = -1\r\n updateJamoCompletion()\r\n }\r\n })\r\n\r\n document.addEventListener('pointerup', (e) => {\r\n isRightClick = e.button === 2\r\n pointerdown.value = false\r\n if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1 && (dragStartPos[0] !== dragEndPos[0] || dragStartPos[1] !== dragEndPos[1])) {\r\n checkJamoCompletion(currentJamoCompletion)\r\n }\r\n })\r\n\r\n function calculateOvershoot(value, min, max) {\r\n if (value < min) {\r\n return min - value\r\n }\r\n if (value > max) {\r\n return value - max\r\n }\r\n return 0\r\n }\r\n\r\n document.addEventListener('pointermove', (e) => {\r\n if (pointerdown.value) {\r\n e.preventDefault()\r\n const jamoElement = document.elementFromPoint(e.clientX, e.clientY)\r\n if (jamoElement?.matches('#jamo-board>i')) {\r\n const pos = indexToPos(jamoElement.dataset.index * 1)\r\n if (dragStartPos[0] === pos[0] && dragStartPos[1] === pos[1]) {\r\n return\r\n }\r\n if (DEBUG) {\r\n jamoBoardElement.querySelector('.mid')?.classList.remove('mid')\r\n jamoElement.classList.add('mid')\r\n }\r\n const dir = isOctilinear(dragStartPos, pos)\r\n if (dir !== -1) {\r\n dragEndPos = pos\r\n dragDir = dir\r\n } else if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\r\n let [cx, cy] = getClosestOctilinearPoint(dragStartPos, pos, dragDir)\r\n if (cx < 0 || cx >= width || cy < 0 || cy >= height) {\r\n const correctedDragDir = isOctilinear(dragStartPos, [cx, cy])\r\n const overshootX = calculateOvershoot(cx, 0, width - 1)\r\n const overshootY = calculateOvershoot(cy, 0, height - 1)\r\n const maxOvershoot = Math.max(overshootX, overshootY);\r\n [cx, cy] = getPosition(cx, cy, correctedDragDir, -maxOvershoot)\r\n }\r\n dragEndPos = [cx, cy]\r\n }\r\n if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\r\n const closestIndex = dragEndPos[1] * width + dragEndPos[0]\r\n const closestElement = jamoElements[closestIndex]\r\n if (DEBUG) {\r\n jamoBoardElement.querySelector('.end')?.classList.remove('end')\r\n closestElement.classList.add('end')\r\n }\r\n updateJamoCompletion()\r\n }\r\n }\r\n }\r\n })\r\n\r\n if (!previousGameState) {\r\n save('gameState', serializeGameState())\r\n }\r\n\r\n window.serializeGameState = serializeGameState\r\n\r\n const mainElement = document.querySelector('main')\r\n\r\n const resizeToFit = () => {\r\n const screenWidth = screen.availWidth\r\n const screenHeight = screen.availHeight\r\n\r\n wordListElement.style.zoom = 1\r\n wordListElement.style.fontSize = '1rem'\r\n jamoBoardElement.style.zoom = 1\r\n mainElement.style.zoom = 1\r\n\r\n const wordListElementWidth = wordListElement.getBoundingClientRect().width\r\n const jamoBoardElementWidth = jamoBoardElement.getBoundingClientRect().width\r\n \r\n wordListElement.style.zoom = Math.min(1, screenWidth / wordListElementWidth)\r\n wordListElement.style.fontSize = `${1 / wordListElement.style.zoom}rem`\r\n jamoBoardElement.style.zoom = Math.min(1, screenWidth / jamoBoardElementWidth)\r\n \r\n const mainElementHeight = mainElement.getBoundingClientRect().height\r\n \r\n mainElement.style.zoom = Math.min(1, screenHeight / mainElementHeight)\r\n }\r\n resizeToFit()\r\n window.addEventListener('resize', resizeToFit)\r\n\r\n window.addEventListener('beforeunload', () => {\r\n const currentStateSaved = load('gameState')\r\n if (currentStateSaved) {\r\n save('gameState', serializeGameState())\r\n }\r\n })\r\n\r\n const gameInitialized = performance.now()\r\n\r\n const PROFILE = false\r\n if (PROFILE) {\r\n requestIdleCallback(() => {\r\n const settled = performance.now()\r\n\r\n const t1 = documentParsed - begin\r\n const t2 = jsParsed - documentParsed\r\n const t3 = gameInitialized - jsParsed\r\n const t4 = settled - gameInitialized\r\n const t5 = settled - begin\r\n \r\n const perfHistory = load('perfHistory', [])\r\n perfHistory.push([t1, t2, t3, t4, t5])\r\n save('perfHistory', perfHistory)\r\n })\r\n }\r\n}\r\n\r\nfunction reset() {\r\n intervals.forEach(clearInterval)\r\n intervals.length = 0\r\n const children = [...document.body.children].filter(elem => elem !== document.currentScript)\r\n children.forEach(child => child.remove())\r\n document.body.innerHTML = initialHTMLWithoutThisScript\r\n preventInitCall = false\r\n init()\r\n}\r\n\r\nfunction load(key, fallback) {\r\n const data = localStorage[key]\r\n if (typeof data === 'string') {\r\n return JSON.parse(data)\r\n }\r\n return fallback ?? null\r\n}\r\n\r\nfunction clear(key) {\r\n delete localStorage[key]\r\n}\r\n\r\nfunction save(key, value) {\r\n localStorage[key] = JSON.stringify(value)\r\n}\r\n\r\nfunction average(arr) {\r\n return arr.reduce((acc, val) => acc + val, 0) / arr.length\r\n}\r\nfunction median(arr) {\r\n const sorted = arr.slice().sort((a, b) => a - b)\r\n const mid = Math.floor(sorted.length / 2)\r\n if (sorted.length % 2 === 0) {\r\n return (sorted[mid - 1] + sorted[mid]) / 2\r\n }\r\n return sorted[mid]\r\n}\r\n\r\nfunction getCurrentFunctionName() {\r\n const currentStackRaw = new Error().stack\r\n const callerLine = currentStackRaw.split('\\n').slice(1)[1]\r\n const caller = callerLine.match(/at (\\w+)/)[1]\r\n return caller\r\n}\r\n\r\nconst jsParsed = performance.now()\r\n\r\ninit()\r\n\r\nfunction registerKonamiCodeHandler() {\r\n const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a', 'Enter']\r\n let konamiCodeIndex = 0\r\n const a = async (e) => {\r\n if (e.key === konamiCode[konamiCodeIndex]) {\r\n konamiCodeIndex++\r\n if (konamiCodeIndex === konamiCode.length) {\r\n showCrazyShit()\r\n window.removeEventListener('keydown', a)\r\n }\r\n } else {\r\n konamiCodeIndex = 0\r\n }\r\n }\r\n window.addEventListener('keydown', a, {passive: true})\r\n}\r\n\r\nfunction showCrazyShit() {\r\n const lunaticText = generateLunaticText()\r\n const lunaticElement = document.createElement('div')\r\n lunaticElement.textContent = lunaticText\r\n document.body.appendChild(lunaticElement)\r\n const slices = createRandomStyleSlices({textLength: lunaticText.length, numSlices: 100, maxLength: 5})\r\n // compute overlapping slices and splice them so that they don't overlap\r\n const sliceGroups = slices.reduce((acc, slice) => {\r\n const last = acc[acc.length - 1]\r\n if (last && last.some(s => s.start < slice.end && s.end > slice.start)) {\r\n last.push(slice)\r\n } else {\r\n acc.push([slice])\r\n }\r\n return acc\r\n }, [])\r\n\r\n}\r\n\r\ndocument.getElementById('show-settings-panel').addEventListener('click', () => {\r\n document.getElementById('settings-panel').showModal()\r\n})\r\n"]}
\ No newline at end of file
diff --git a/src/css.css b/src/css.css
index 67bb5b8..54aed01 100644
--- a/src/css.css
+++ b/src/css.css
@@ -306,7 +306,7 @@ main>* {
}
#file-wrapper::after {
- content: 'ํ์ผ ๋ถ๋ฌ์ค๊ธฐ'; /* Text below emoji */
+ content: attr(data-text); /* Text below emoji */
display: block;
font-size: 14px; /* Adjust text size */
}
diff --git a/src/index.html b/src/index.html
index 55b7d7a..9e0564e 100644
--- a/src/index.html
+++ b/src/index.html
@@ -51,7 +51,7 @@ ๊ฒ์ ์ค์
-
+