scrabble-solver 2.17.0 → 2.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. package/.github/workflows/bunx.yml +13 -10
  2. package/.github/workflows/e2e.yml +7 -9
  3. package/.github/workflows/npx.yml +13 -10
  4. package/.nx/workspace-data/d/server-process.json +2 -2
  5. package/.nx/workspace-data/file-map.json +1406 -1406
  6. package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
  7. package/.nx/workspace-data/lockfile-nodes.hash +1 -1
  8. package/.nx/workspace-data/nx_files.nxt +0 -0
  9. package/.nx/workspace-data/parsed-lock-file.dependencies.json +212 -1317
  10. package/.nx/workspace-data/parsed-lock-file.nodes.json +758 -2693
  11. package/.nx/workspace-data/project-graph.json +1076 -4158
  12. package/.nx/workspace-data/source-maps.json +204 -0
  13. package/.oxlintrc.json +8 -11
  14. package/CLAUDE.md +18 -15
  15. package/README.md +8 -1
  16. package/bun.lock +230 -499
  17. package/e2e/app.spec.ts +124 -0
  18. package/e2e/bugs/112-character-bonus.spec.ts +25 -0
  19. package/e2e/bugs/129-esc-sidebar.spec.ts +17 -0
  20. package/e2e/bugs/2-q-tile-in-english.spec.ts +19 -0
  21. package/e2e/bugs/3-x-tile-in-polish.spec.ts +19 -0
  22. package/e2e/bugs/30-click-inside-radio-icon.spec.ts +25 -0
  23. package/e2e/bugs/333-disabled-radio-cursor.spec.ts +18 -0
  24. package/e2e/bugs/342-results-sorting.spec.ts +30 -0
  25. package/e2e/bugs/352-dictionary-race-condition.spec.ts +27 -0
  26. package/e2e/bugs/363-dictionary-input-unique-words.spec.ts +27 -0
  27. package/e2e/bugs/380-transliteration-when-pasting.spec.ts +55 -0
  28. package/e2e/bugs/398-digraphs-in-created-words.spec.ts +23 -0
  29. package/e2e/bugs/401-board-state-on-game-change.spec.ts +24 -0
  30. package/e2e/features/198-highlight-unreachable-cells.spec.ts +85 -0
  31. package/e2e/features/91-hover-remaining-tile-highlights.spec.ts +76 -0
  32. package/e2e/features/hidden-coordinates-migration.spec.ts +27 -0
  33. package/e2e/features/solver-paths.spec.ts +45 -0
  34. package/e2e/lib/actions.ts +119 -0
  35. package/e2e/lib/selectors.ts +71 -0
  36. package/e2e/tsconfig.json +10 -0
  37. package/lerna.json +1 -1
  38. package/package.json +30 -52
  39. package/packages/configs/package.json +13 -4
  40. package/packages/constants/package.json +6 -2
  41. package/packages/dictionaries/package.json +12 -5
  42. package/packages/logger/package.json +7 -2
  43. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  44. package/packages/scrabble-solver/.next/build-manifest.json +22 -20
  45. package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
  46. package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
  47. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  48. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  49. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  50. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
  51. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  52. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  53. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  54. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  55. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
  56. package/packages/scrabble-solver/.next/diagnostics/framework.json +1 -1
  57. package/packages/scrabble-solver/.next/dynamic-css-manifest.json +8 -1
  58. package/packages/scrabble-solver/.next/next-minimal-server.js.nft.json +1 -1
  59. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  60. package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
  61. package/packages/scrabble-solver/.next/react-loadable-manifest.json +176 -1
  62. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  63. package/packages/scrabble-solver/.next/server/chunks/309.js +1 -0
  64. package/packages/scrabble-solver/.next/server/chunks/{785.js → 316.js} +1 -1
  65. package/packages/scrabble-solver/.next/server/chunks/344.js +1 -0
  66. package/packages/scrabble-solver/.next/server/chunks/371.js +1 -0
  67. package/packages/scrabble-solver/.next/server/chunks/465.js +1 -0
  68. package/packages/scrabble-solver/.next/server/chunks/546.js +1 -0
  69. package/packages/scrabble-solver/.next/server/chunks/623.js +1 -0
  70. package/packages/scrabble-solver/.next/server/chunks/624.js +1 -0
  71. package/packages/scrabble-solver/.next/server/chunks/641.js +1 -0
  72. package/packages/scrabble-solver/.next/server/chunks/671.js +1 -1
  73. package/packages/scrabble-solver/.next/server/chunks/692.js +1 -0
  74. package/packages/scrabble-solver/.next/server/chunks/705.js +1 -0
  75. package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
  76. package/packages/scrabble-solver/.next/server/chunks/846.js +1 -0
  77. package/packages/scrabble-solver/.next/server/chunks/853.js +15 -0
  78. package/packages/scrabble-solver/.next/server/chunks/886.js +1 -0
  79. package/packages/scrabble-solver/.next/server/chunks/916.js +1 -0
  80. package/packages/scrabble-solver/.next/server/chunks/953.js +1 -0
  81. package/packages/scrabble-solver/.next/server/chunks/961.js +1 -0
  82. package/packages/scrabble-solver/.next/server/chunks/968.js +1 -0
  83. package/packages/scrabble-solver/.next/server/dynamic-css-manifest.js +1 -1
  84. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  85. package/packages/scrabble-solver/.next/server/middleware-react-loadable-manifest.js +1 -1
  86. package/packages/scrabble-solver/.next/server/pages/404.html +29 -1
  87. package/packages/scrabble-solver/.next/server/pages/500.html +29 -1
  88. package/packages/scrabble-solver/.next/server/pages/_app.js +1 -1
  89. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  90. package/packages/scrabble-solver/.next/server/pages/_document.js +28 -1
  91. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  92. package/packages/scrabble-solver/.next/server/pages/_error.js +28 -1
  93. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  94. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  95. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  96. package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
  97. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  98. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  99. package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
  100. package/packages/scrabble-solver/.next/server/pages/index.html +29 -1
  101. package/packages/scrabble-solver/.next/server/pages/index.js +30 -1
  102. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  103. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  104. package/packages/scrabble-solver/.next/server/pages/not-found.html +29 -1
  105. package/packages/scrabble-solver/.next/server/pages/not-found.js.nft.json +1 -1
  106. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  107. package/packages/scrabble-solver/.next/server/webpack-api-runtime.js +1 -1
  108. package/packages/scrabble-solver/.next/server/webpack-runtime.js +1 -1
  109. package/packages/scrabble-solver/.next/static/5KOWzYqfY1Z2rjrzptr8D/_buildManifest.js +1 -0
  110. package/packages/scrabble-solver/.next/static/chunks/101-829ab8a8d310e7a0.js +3 -0
  111. package/packages/scrabble-solver/.next/static/chunks/106.e414e88694add27f.js +1 -0
  112. package/packages/scrabble-solver/.next/static/chunks/127.125d896a19061d71.js +1 -0
  113. package/packages/scrabble-solver/.next/static/chunks/18.8b91e137d239e15c.js +1 -0
  114. package/packages/scrabble-solver/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
  115. package/packages/scrabble-solver/.next/static/chunks/307.67f52f13e3d60d4e.js +1 -0
  116. package/packages/scrabble-solver/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
  117. package/packages/scrabble-solver/.next/static/chunks/363.01eed3233f92b6a4.js +1 -0
  118. package/packages/scrabble-solver/.next/static/chunks/422.1559ae6756912ca5.js +1 -0
  119. package/packages/scrabble-solver/.next/static/chunks/541.dcd9e8b804f88da5.js +1 -0
  120. package/packages/scrabble-solver/.next/static/chunks/631.98d42abad0a60af6.js +1 -0
  121. package/packages/scrabble-solver/.next/static/chunks/663.d2c9ca36b26695b8.js +1 -0
  122. package/packages/scrabble-solver/.next/static/chunks/711.ba7b8f900dfd40d0.js +1 -0
  123. package/packages/scrabble-solver/.next/static/chunks/744.b06c9a3de4a1d877.js +1 -0
  124. package/packages/scrabble-solver/.next/static/chunks/820.97b82f751ca49579.js +1 -0
  125. package/packages/scrabble-solver/.next/static/chunks/865.a9b563a45848a83c.js +1 -0
  126. package/packages/scrabble-solver/.next/static/chunks/912.ff8037ba9d4db5c8.js +1 -0
  127. package/packages/scrabble-solver/.next/static/chunks/939.19b33a3d3c351d73.js +1 -0
  128. package/packages/scrabble-solver/.next/static/chunks/990.61296e5870b91f11.js +1 -0
  129. package/packages/scrabble-solver/.next/static/chunks/framework-626cbd48ac5aca57.js +1 -0
  130. package/packages/scrabble-solver/.next/static/chunks/main-b92e08f3fc96dd8d.js +5 -0
  131. package/packages/scrabble-solver/.next/static/chunks/pages/_app-2e607dcd4135f340.js +1 -0
  132. package/packages/scrabble-solver/.next/static/chunks/pages/{_error-afeda7c5231b6c3c.js → _error-f0290a98eb3ed0ec.js} +1 -1
  133. package/packages/scrabble-solver/.next/static/chunks/pages/index-e0087247c995328d.js +1 -0
  134. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-a59f9deeeff63d17.js +1 -0
  135. package/packages/scrabble-solver/.next/static/chunks/webpack-cfb34d175e081981.js +1 -0
  136. package/packages/scrabble-solver/.next/static/css/2d03f0da2c678dfc.css +1 -0
  137. package/packages/scrabble-solver/.next/static/css/4dbcacc1122ac6b9.css +2 -0
  138. package/packages/scrabble-solver/.next/static/css/7b6724d2df70f48f.css +1 -0
  139. package/packages/scrabble-solver/.next/static/css/b736be3bd02b5d99.css +1 -0
  140. package/packages/scrabble-solver/.next/static/css/b74cd7de82dabcdd.css +1 -0
  141. package/packages/scrabble-solver/.next/static/css/c403ce739a9c0d83.css +1 -0
  142. package/packages/scrabble-solver/.next/static/css/ca311e6c030663d4.css +1 -0
  143. package/packages/scrabble-solver/.next/static/css/dee878eb64099d92.css +1 -0
  144. package/packages/scrabble-solver/.next/static/css/fbfdce70731d0d2b.css +1 -0
  145. package/packages/scrabble-solver/.next/trace +21 -21
  146. package/packages/scrabble-solver/.next/trace-build +1 -1
  147. package/packages/scrabble-solver/empty-module.js +2 -0
  148. package/packages/scrabble-solver/next.config.js +13 -2
  149. package/packages/scrabble-solver/package.json +42 -16
  150. package/packages/scrabble-solver/public/llms.txt +11 -0
  151. package/packages/scrabble-solver/public/robots.txt +5 -0
  152. package/packages/scrabble-solver/public/service-worker.js +1 -1
  153. package/packages/scrabble-solver/public/site.webmanifest +14 -0
  154. package/packages/scrabble-solver/scripts/generate-scss-types.ts +112 -0
  155. package/packages/scrabble-solver/src/@types/scss.d.ts +6 -9
  156. package/packages/scrabble-solver/src/@types/svg.d.ts +1 -1
  157. package/packages/scrabble-solver/src/components/Alert/Alert.module.scss.d.ts +13 -0
  158. package/packages/scrabble-solver/src/components/Alert/Alert.tsx +4 -1
  159. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss.d.ts +6 -0
  160. package/packages/scrabble-solver/src/components/Board/Board.module.scss +10 -0
  161. package/packages/scrabble-solver/src/components/Board/Board.module.scss.d.ts +17 -0
  162. package/packages/scrabble-solver/src/components/Board/Board.tsx +14 -26
  163. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +54 -49
  164. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +18 -18
  165. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss.d.ts +7 -0
  166. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +6 -3
  167. package/packages/scrabble-solver/src/components/Board/components/Actions/lib.ts +3 -1
  168. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss.d.ts +13 -0
  169. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +28 -33
  170. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss.d.ts +10 -0
  171. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.tsx +1 -1
  172. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss.d.ts +8 -0
  173. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
  174. package/packages/scrabble-solver/src/components/Board/getBoardBackground.ts +175 -0
  175. package/packages/scrabble-solver/src/components/Board/hooks/index.ts +0 -1
  176. package/packages/scrabble-solver/src/components/Board/hooks/useBoardStyle.ts +10 -20
  177. package/packages/scrabble-solver/src/components/Board/hooks/useFloatingActions.ts +3 -6
  178. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +6 -3
  179. package/packages/scrabble-solver/src/components/Board/selectors.ts +1 -1
  180. package/packages/scrabble-solver/src/components/Button/Button.module.scss.d.ts +12 -0
  181. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +1 -0
  182. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss.d.ts +14 -0
  183. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +1 -3
  184. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.module.scss.d.ts +7 -0
  185. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
  186. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.module.scss.d.ts +7 -0
  187. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.tsx +1 -1
  188. package/packages/scrabble-solver/src/components/IconButton/IconButton.module.scss.d.ts +8 -0
  189. package/packages/scrabble-solver/src/components/Key/Key.module.scss.d.ts +6 -0
  190. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.module.scss.d.ts +11 -0
  191. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.tsx +4 -1
  192. package/packages/scrabble-solver/src/components/Keys/index.tsx +1 -1
  193. package/packages/scrabble-solver/src/components/Loading/Loading.module.scss.d.ts +9 -0
  194. package/packages/scrabble-solver/src/components/Loading/Loading.tsx +1 -1
  195. package/packages/scrabble-solver/src/components/Logo/Logo.tsx +4 -9
  196. package/packages/scrabble-solver/src/components/Logo/LogoBlueprint.tsx +1 -0
  197. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss.d.ts +15 -0
  198. package/packages/scrabble-solver/src/components/Modal/Modal.tsx +16 -3
  199. package/packages/scrabble-solver/src/components/Modal/components/Section/Section.module.scss.d.ts +7 -0
  200. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss +20 -0
  201. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss.d.ts +13 -0
  202. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +29 -45
  203. package/packages/scrabble-solver/src/components/NotFound/NotFound.module.scss.d.ts +9 -0
  204. package/packages/scrabble-solver/src/components/NotFound/NotFound.tsx +1 -1
  205. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.module.scss.d.ts +11 -0
  206. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.tsx +0 -1
  207. package/packages/scrabble-solver/src/components/PlainTiles/Tile.tsx +3 -4
  208. package/packages/scrabble-solver/src/components/PlainTiles/lib.ts +4 -19
  209. package/packages/scrabble-solver/src/components/PlainTiles/types.ts +0 -1
  210. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss +7 -0
  211. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss.d.ts +6 -0
  212. package/packages/scrabble-solver/src/components/Progress/Progress.tsx +2 -11
  213. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +3 -0
  214. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss.d.ts +10 -0
  215. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +8 -11
  216. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +2 -0
  217. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss.d.ts +7 -0
  218. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -13
  219. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/lib.ts +1 -1
  220. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss.d.ts +6 -0
  221. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.tsx +5 -4
  222. package/packages/scrabble-solver/src/components/Rack/selectors.ts +1 -1
  223. package/packages/scrabble-solver/src/components/Radio/Radio.module.scss.d.ts +12 -0
  224. package/packages/scrabble-solver/src/components/Results/Cell.tsx +3 -12
  225. package/packages/scrabble-solver/src/components/Results/Header.tsx +9 -18
  226. package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +4 -5
  227. package/packages/scrabble-solver/src/components/Results/Result.tsx +4 -12
  228. package/packages/scrabble-solver/src/components/Results/Results.module.scss +4 -6
  229. package/packages/scrabble-solver/src/components/Results/Results.module.scss.d.ts +27 -0
  230. package/packages/scrabble-solver/src/components/Results/Results.tsx +4 -3
  231. package/packages/scrabble-solver/src/components/Results/SolveButton.tsx +1 -1
  232. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.module.scss.d.ts +7 -0
  233. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.tsx +1 -1
  234. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +6 -5
  235. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss.d.ts +20 -0
  236. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +19 -21
  237. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss.d.ts +7 -0
  238. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
  239. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +18 -0
  240. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +3 -1
  241. package/packages/scrabble-solver/src/components/Spinner/Spinner.module.scss.d.ts +10 -0
  242. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +3 -0
  243. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss.d.ts +19 -0
  244. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +5 -14
  245. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +2 -12
  246. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss.d.ts +7 -0
  247. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.tsx +14 -3
  248. package/packages/scrabble-solver/src/components/Tooltip/useTooltip.ts +1 -0
  249. package/packages/scrabble-solver/src/hooks/useColumns.ts +4 -7
  250. package/packages/scrabble-solver/src/hooks/useDeferredRender.ts +20 -0
  251. package/packages/scrabble-solver/src/hooks/useDirection.ts +4 -3
  252. package/packages/scrabble-solver/src/hooks/useLanguage.ts +2 -2
  253. package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +32 -8
  254. package/packages/scrabble-solver/src/hooks/useMedia.ts +26 -26
  255. package/packages/scrabble-solver/src/i18n/constants.ts +2 -15
  256. package/packages/scrabble-solver/src/i18n/i18n.ts +18 -17
  257. package/packages/scrabble-solver/src/i18n/languages/english.json +0 -1
  258. package/packages/scrabble-solver/src/i18n/languages/french.json +0 -1
  259. package/packages/scrabble-solver/src/i18n/languages/german.json +0 -1
  260. package/packages/scrabble-solver/src/i18n/languages/persian.json +0 -1
  261. package/packages/scrabble-solver/src/i18n/languages/polish.json +0 -1
  262. package/packages/scrabble-solver/src/i18n/languages/romanian.json +0 -1
  263. package/packages/scrabble-solver/src/i18n/languages/spanish.json +0 -1
  264. package/packages/scrabble-solver/src/i18n/languages/turkish.json +0 -1
  265. package/packages/scrabble-solver/src/i18n/localeIcons.ts +24 -0
  266. package/packages/scrabble-solver/src/lib/localeTransliterate.test.ts +5 -0
  267. package/packages/scrabble-solver/src/lib/localeTransliterate.ts +26 -6
  268. package/packages/scrabble-solver/src/lib/waitForIdleOrFirstIntent.ts +44 -0
  269. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.module.scss.d.ts +8 -0
  270. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.tsx +3 -1
  271. package/packages/scrabble-solver/src/modals/KeyMapModal/KeyMapModal.tsx +3 -1
  272. package/packages/scrabble-solver/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss.d.ts +11 -0
  273. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +5 -0
  274. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss.d.ts +10 -0
  275. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +16 -5
  276. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +4 -1
  277. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss.d.ts +9 -0
  278. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -6
  279. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  280. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss.d.ts +12 -0
  281. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.tsx +5 -12
  282. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss.d.ts +8 -0
  283. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +8 -4
  284. package/packages/scrabble-solver/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  285. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.module.scss.d.ts +7 -0
  286. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
  287. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss.d.ts +7 -0
  288. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
  289. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss.d.ts +7 -0
  290. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +1 -1
  291. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss.d.ts +7 -0
  292. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
  293. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss.d.ts +8 -0
  294. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +24 -21
  295. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss.d.ts +7 -0
  296. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +1 -1
  297. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.module.scss.d.ts +7 -0
  298. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -5
  299. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.module.scss.d.ts +11 -0
  300. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.tsx +4 -2
  301. package/packages/scrabble-solver/src/modals/preload.ts +24 -0
  302. package/packages/scrabble-solver/src/pages/_app.tsx +37 -76
  303. package/packages/scrabble-solver/src/pages/_document.tsx +112 -24
  304. package/packages/scrabble-solver/src/pages/api/solve.ts +1 -1
  305. package/packages/scrabble-solver/src/pages/index.module.scss +1 -0
  306. package/packages/scrabble-solver/src/pages/index.module.scss.d.ts +11 -0
  307. package/packages/scrabble-solver/src/pages/index.tsx +127 -40
  308. package/packages/scrabble-solver/src/pages/not-found.tsx +14 -3
  309. package/packages/scrabble-solver/src/parameters/index.ts +31 -93
  310. package/packages/scrabble-solver/src/serviceWorkerManager.ts +5 -2
  311. package/packages/scrabble-solver/src/state/actions.ts +1 -1
  312. package/packages/scrabble-solver/src/state/app/index.ts +4 -0
  313. package/packages/scrabble-solver/src/state/app/initialState.ts +6 -0
  314. package/packages/scrabble-solver/src/state/app/selectors.ts +5 -0
  315. package/packages/scrabble-solver/src/state/app/slice.ts +20 -0
  316. package/packages/scrabble-solver/src/state/app/types.ts +4 -0
  317. package/packages/scrabble-solver/src/state/board/initialState.ts +1 -2
  318. package/packages/scrabble-solver/src/state/i18n/index.ts +4 -0
  319. package/packages/scrabble-solver/src/state/i18n/initialState.ts +5 -0
  320. package/packages/scrabble-solver/src/state/i18n/selectors.ts +3 -0
  321. package/packages/scrabble-solver/src/state/i18n/slice.ts +16 -0
  322. package/packages/scrabble-solver/src/state/i18n/types.ts +7 -0
  323. package/packages/scrabble-solver/src/state/index.ts +2 -0
  324. package/packages/scrabble-solver/src/state/lib.ts +1 -1
  325. package/packages/scrabble-solver/src/state/localStorage.ts +86 -29
  326. package/packages/scrabble-solver/src/state/rack/initialState.ts +1 -2
  327. package/packages/scrabble-solver/src/state/rack/slice.ts +4 -1
  328. package/packages/scrabble-solver/src/state/results/lib.ts +5 -5
  329. package/packages/scrabble-solver/src/state/results/selectors.ts +1 -1
  330. package/packages/scrabble-solver/src/state/sagas.ts +125 -9
  331. package/packages/scrabble-solver/src/state/selectors.ts +1 -1
  332. package/packages/scrabble-solver/src/state/settings/initialState.ts +7 -10
  333. package/packages/scrabble-solver/src/state/settings/selectors.ts +7 -2
  334. package/packages/scrabble-solver/src/state/settings/slice.ts +5 -2
  335. package/packages/scrabble-solver/src/state/store.ts +4 -0
  336. package/packages/scrabble-solver/src/state/types.ts +4 -0
  337. package/packages/scrabble-solver/src/styles/_tokens.scss +1 -0
  338. package/packages/scrabble-solver/src/styles/global.scss +9 -0
  339. package/packages/scrabble-solver/src/styles/variables.module.scss +0 -21
  340. package/packages/scrabble-solver/src/styles/variables.module.scss.d.ts +27 -0
  341. package/packages/scrabble-solver/src/styles/variables.scss +46 -2
  342. package/packages/scrabble-solver/src/types/index.ts +1 -2
  343. package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
  344. package/packages/solver/package.json +14 -6
  345. package/packages/types/build/Direction.d.ts +1 -0
  346. package/packages/types/build/Direction.js +2 -0
  347. package/packages/types/build/ShowCoordinates.d.ts +1 -2
  348. package/packages/types/build/TextDirection.d.ts +1 -0
  349. package/packages/types/build/TextDirection.js +2 -0
  350. package/packages/types/build/index.d.ts +2 -0
  351. package/packages/types/package.json +10 -3
  352. package/packages/types/src/Direction.ts +1 -0
  353. package/packages/types/src/ShowCoordinates.ts +1 -2
  354. package/packages/types/src/TextDirection.ts +1 -0
  355. package/packages/types/src/index.ts +2 -0
  356. package/packages/word-definitions/package.json +9 -3
  357. package/packages/word-lists/package.json +11 -3
  358. package/playwright-report/index.html +49 -0
  359. package/playwright.config.ts +30 -0
  360. package/screencast.gif +0 -0
  361. package/scripts/record-screencast.ts +347 -0
  362. package/test-results/.last-run.json +4 -0
  363. package/tsconfig.json +1 -1
  364. package/babel.config.js +0 -4
  365. package/cypress/e2e/app.spec.cy.ts +0 -123
  366. package/cypress/e2e/bugs/112-character-bonus.ts +0 -40
  367. package/cypress/e2e/bugs/129-esc-sidebar.ts +0 -23
  368. package/cypress/e2e/bugs/2-q-tile-in-english.ts +0 -33
  369. package/cypress/e2e/bugs/3-x-tile-in-polish.ts +0 -33
  370. package/cypress/e2e/bugs/30-click-inside-radio-icon.ts +0 -26
  371. package/cypress/e2e/bugs/333-disabled-radio-cursor.ts +0 -24
  372. package/cypress/e2e/bugs/342-results-sorting.ts +0 -48
  373. package/cypress/e2e/bugs/348-results-word-padding.ts +0 -74
  374. package/cypress/e2e/bugs/352-dictionary-race-condition.ts +0 -29
  375. package/cypress/e2e/bugs/363-dictionary-input-unique-words.ts +0 -41
  376. package/cypress/e2e/bugs/380-transliteration-when-pasting.ts +0 -75
  377. package/cypress/e2e/bugs/398-digraphs-in-created-words.ts +0 -38
  378. package/cypress/e2e/bugs/401-board-state-on-game-change.ts +0 -36
  379. package/cypress/e2e/bugs.spec.cy.ts +0 -13
  380. package/cypress/e2e/features/198-highlight-unreachable-cells.ts +0 -101
  381. package/cypress/e2e/features/91-hover-remaining-tile-highlights.ts +0 -90
  382. package/cypress/e2e/features/solver-paths.ts +0 -66
  383. package/cypress/e2e/features.spec.cy.ts +0 -3
  384. package/cypress/support/commands.ts +0 -4
  385. package/cypress/support/e2e.ts +0 -1
  386. package/cypress/support/index.ts +0 -1
  387. package/cypress/support/lib/actions.ts +0 -104
  388. package/cypress/support/lib/selectors.ts +0 -70
  389. package/cypress/tsconfig.json +0 -10
  390. package/cypress.config.ts +0 -17
  391. package/packages/scrabble-solver/.next/server/chunks/129.js +0 -1
  392. package/packages/scrabble-solver/.next/server/chunks/318.js +0 -3
  393. package/packages/scrabble-solver/.next/server/chunks/791.js +0 -15
  394. package/packages/scrabble-solver/.next/static/OLE8_ZInlGH7ENU0NIOpf/_buildManifest.js +0 -1
  395. package/packages/scrabble-solver/.next/static/chunks/830.661816bf0de8c2f1.js +0 -1
  396. package/packages/scrabble-solver/.next/static/chunks/framework-3cf15b8041c59dd3.js +0 -1
  397. package/packages/scrabble-solver/.next/static/chunks/main-5b999f3f24ebfa84.js +0 -5
  398. package/packages/scrabble-solver/.next/static/chunks/pages/_app-7024e7a27ffbb2fa.js +0 -7
  399. package/packages/scrabble-solver/.next/static/chunks/pages/index-8b398b0a5d870773.js +0 -1
  400. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-9e076427bad69d90.js +0 -1
  401. package/packages/scrabble-solver/.next/static/chunks/webpack-3943d624bbb47a64.js +0 -1
  402. package/packages/scrabble-solver/.next/static/css/a78e04fe95452380.css +0 -1
  403. package/packages/scrabble-solver/.next/static/css/aedeaada218d7280.css +0 -2
  404. package/packages/scrabble-solver/src/app-layout/AppLayoutContext.tsx +0 -25
  405. package/packages/scrabble-solver/src/app-layout/index.ts +0 -1
  406. package/packages/scrabble-solver/src/app-layout/useAppLayoutValue.ts +0 -112
  407. package/packages/scrabble-solver/src/components/Board/hooks/useBackgroundImage.tsx +0 -279
  408. package/packages/scrabble-solver/src/components/SeoMessage/SeoMessage.tsx +0 -18
  409. package/packages/scrabble-solver/src/components/SeoMessage/index.ts +0 -1
  410. package/packages/scrabble-solver/src/components/index.ts +0 -26
  411. package/packages/scrabble-solver/src/hooks/index.ts +0 -12
  412. package/packages/scrabble-solver/src/hooks/useOnWindowResize.ts +0 -11
  413. package/packages/scrabble-solver/src/hooks/useViewportSize.ts +0 -17
  414. package/packages/scrabble-solver/src/i18n/index.ts +0 -2
  415. package/packages/scrabble-solver/src/icons/Flag.svg +0 -4
  416. package/packages/scrabble-solver/src/icons/Star.svg +0 -4
  417. package/packages/scrabble-solver/src/icons/index.ts +0 -49
  418. package/packages/scrabble-solver/src/lib/getTileSizes.ts +0 -13
  419. package/packages/scrabble-solver/src/lib/index.ts +0 -24
  420. package/packages/scrabble-solver/src/modals/index.ts +0 -7
  421. /package/{cypress/support → e2e}/lib/index.ts +0 -0
  422. /package/packages/scrabble-solver/.next/static/{OLE8_ZInlGH7ENU0NIOpf → 5KOWzYqfY1Z2rjrzptr8D}/_ssgManifest.js +0 -0
@@ -1,31 +1,77 @@
1
- /* eslint-disable max-statements */
1
+ /* eslint-disable max-lines, max-statements */
2
2
 
3
+ import { games } from '@scrabble-solver/configs';
3
4
  import { isObject } from '@scrabble-solver/types';
4
5
  import { execSync } from 'child_process';
5
6
  import fs from 'fs';
7
+ import dynamic from 'next/dynamic';
6
8
  import path from 'path';
7
- import { type FunctionComponent, useCallback, useState } from 'react';
8
- import ReactModal from 'react-modal';
9
+ import { type FunctionComponent, useCallback, useEffect, useState } from 'react';
9
10
  import { useDispatch } from 'react-redux';
10
11
 
11
- import { Logo, NavButtons, Solver } from '@/components';
12
- import { useDirection, useEffectOnce, useLanguage, useLocalStorage } from '@/hooks';
13
- import { LOCALE_FEATURES } from '@/i18n';
14
- import {
15
- DictionaryModal,
16
- KeyMapModal,
17
- MenuModal,
18
- RemainingTilesModal,
19
- ResultsModal,
20
- SettingsModal,
21
- WordsModal,
22
- } from '@/modals';
12
+ import { Logo } from '@/components/Logo';
13
+ import { NavButtons } from '@/components/NavButtons';
14
+ import { Solver } from '@/components/Solver';
15
+ import { useDirection } from '@/hooks/useDirection';
16
+ import { useEffectOnce } from '@/hooks/useEffectOnce';
17
+ import { useLanguage } from '@/hooks/useLanguage';
18
+ import { useLocalStorage } from '@/hooks/useLocalStorage';
19
+ import { LOCALE_FEATURES } from '@/i18n/constants';
20
+ import { schedulePreloadModals } from '@/modals/preload';
21
+ import { CONFIG_PENDING_CLASS, GITHUB_PROJECT_URL, NPM_PACKAGE_URL, SITE_DESCRIPTION, SITE_URL } from '@/parameters';
23
22
  import { registerServiceWorker } from '@/serviceWorkerManager';
24
- import { initialize, reset, selectConfig, selectLocale, useTypedSelector } from '@/state';
23
+ import { initialize, reset, selectConfig, selectIsHydrated, selectLocale, useTypedSelector } from '@/state';
25
24
 
26
25
  import styles from './index.module.scss';
27
26
 
28
- ReactModal.setAppElement('#__next');
27
+ const DictionaryModal = dynamic(() => import('@/modals/DictionaryModal').then((module) => module.DictionaryModal), {
28
+ ssr: false,
29
+ });
30
+ const KeyMapModal = dynamic(() => import('@/modals/KeyMapModal').then((module) => module.KeyMapModal), {
31
+ ssr: false,
32
+ });
33
+ const MenuModal = dynamic(() => import('@/modals/MenuModal').then((module) => module.MenuModal), {
34
+ ssr: false,
35
+ });
36
+ const RemainingTilesModal = dynamic(
37
+ () => import('@/modals/RemainingTilesModal').then((module) => module.RemainingTilesModal),
38
+ {
39
+ ssr: false,
40
+ },
41
+ );
42
+ const ResultsModal = dynamic(() => import('@/modals/ResultsModal').then((module) => module.ResultsModal), {
43
+ ssr: false,
44
+ });
45
+ const SettingsModal = dynamic(() => import('@/modals/SettingsModal').then((module) => module.SettingsModal), {
46
+ ssr: false,
47
+ });
48
+ const WordsModal = dynamic(() => import('@/modals/WordsModal').then((module) => module.WordsModal), {
49
+ ssr: false,
50
+ });
51
+
52
+ const WEB_APPLICATION_JSON_LD = JSON.stringify({
53
+ '@context': 'https://schema.org',
54
+ '@type': 'WebApplication',
55
+ name: 'Scrabble Solver',
56
+ alternateName: 'Scrabble Solver 2',
57
+ url: `${SITE_URL}/`,
58
+ description: SITE_DESCRIPTION,
59
+ applicationCategory: 'GameApplication',
60
+ operatingSystem: 'Any',
61
+ browserRequirements: 'Requires JavaScript',
62
+ image: `${SITE_URL}/og.png`,
63
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
64
+ inLanguage: ['en-US', 'en-GB', 'de-DE', 'es-ES', 'fa-IR', 'fr-FR', 'pl-PL', 'ro-RO', 'tr-TR'],
65
+ featureList: [
66
+ 'Finds the highest-scoring words for a given board and rack',
67
+ 'Supports Scrabble, Super Scrabble, Scrabble Duel, Letter League, Crossplay, Literaki, and Kelimelik',
68
+ 'Dictionaries and word definitions in 8 languages',
69
+ 'Works offline once a dictionary is cached',
70
+ 'Free, open source, no ads, no sign-up',
71
+ ],
72
+ sameAs: [GITHUB_PROJECT_URL, NPM_PACKAGE_URL],
73
+ author: { '@type': 'Person', name: 'Kamil Mielnik', url: 'https://kamilmielnik.com' },
74
+ });
29
75
 
30
76
  interface Props {
31
77
  version: string;
@@ -37,7 +83,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
37
83
  const dispatch = useDispatch();
38
84
  const config = useTypedSelector(selectConfig);
39
85
  const locale = useTypedSelector(selectLocale);
40
- const [isClient, setIsClient] = useState(false);
86
+ const isHydrated = useTypedSelector(selectIsHydrated);
41
87
  const [modals, setModals] = useState<Record<Modal, boolean>>({
42
88
  dictionary: false,
43
89
  keyMap: false,
@@ -47,9 +93,26 @@ const Index: FunctionComponent<Props> = ({ version }) => {
47
93
  settings: false,
48
94
  words: false,
49
95
  });
96
+ const [mountedModals, setMountedModals] = useState<Partial<Record<Modal, boolean>>>({});
50
97
 
51
98
  const patchModals = useCallback((patch: Partial<Record<Modal, boolean>>) => {
52
99
  setModals((current) => ({ ...current, ...patch }));
100
+ setMountedModals((current) => {
101
+ const newModals = Object.keys(patch) as Modal[];
102
+ const newOpenedModals = newModals.filter((modal) => patch[modal] && !current[modal]);
103
+
104
+ if (newOpenedModals.length === 0) {
105
+ return current;
106
+ }
107
+
108
+ const mounted = { ...current };
109
+
110
+ for (const modal of newOpenedModals) {
111
+ mounted[modal] = true;
112
+ }
113
+
114
+ return mounted;
115
+ });
53
116
  }, []);
54
117
 
55
118
  const handleClear = useCallback(() => dispatch(reset()), [dispatch]);
@@ -73,29 +136,48 @@ const Index: FunctionComponent<Props> = ({ version }) => {
73
136
  useLanguage(locale);
74
137
  useLocalStorage();
75
138
 
139
+ useEffect(() => {
140
+ const root = document.documentElement;
141
+ const variables = [
142
+ { name: '--board-cols', value: config.boardWidth, defaultValue: games.scrabble.boardWidth },
143
+ { name: '--board-rows', value: config.boardHeight, defaultValue: games.scrabble.boardHeight },
144
+ { name: '--rack-size', value: config.rackSize, defaultValue: games.scrabble.rackSize },
145
+ ];
146
+
147
+ for (const { name, value, defaultValue } of variables) {
148
+ const current = root.style.getPropertyValue(name) || String(defaultValue);
149
+
150
+ if (current !== String(value)) {
151
+ root.style.setProperty(name, String(value));
152
+ }
153
+ }
154
+ }, [config.boardHeight, config.boardWidth, config.rackSize]);
155
+
156
+ useEffect(() => {
157
+ if (isHydrated) {
158
+ document.documentElement.classList.remove(CONFIG_PENDING_CLASS);
159
+ }
160
+ }, [isHydrated]);
161
+
76
162
  useEffectOnce(() => {
77
163
  if (process.env.NODE_ENV === 'production') {
78
164
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
79
165
  registerServiceWorker();
80
166
  }
81
167
 
82
- setIsClient(true);
83
- dispatch(initialize());
168
+ dispatch(initialize({ version }));
169
+ schedulePreloadModals();
84
170
  });
85
171
 
86
- if (!isClient) {
87
- return null;
88
- }
89
-
90
172
  return (
91
173
  <>
92
174
  <nav className={styles.nav}>
93
175
  <div className={styles.navContent}>
94
- <div className={styles.navLogo}>
176
+ <h1 className={styles.navLogo}>
95
177
  <a className={styles.logoContainer} href="/" title={version}>
96
178
  <Logo className={styles.logo} />
97
179
  </a>
98
- </div>
180
+ </h1>
99
181
 
100
182
  <NavButtons
101
183
  onClear={handleClear}
@@ -112,28 +194,33 @@ const Index: FunctionComponent<Props> = ({ version }) => {
112
194
  <Solver className={styles.solver} onShowResults={handleShowResults} />
113
195
  </main>
114
196
 
115
- <MenuModal
116
- isOpen={modals.menu}
117
- onClose={handleCloseMenu}
118
- onShowDictionary={handleShowDictionary}
119
- onShowRemainingTiles={handleShowRemainingTiles}
120
- onShowSettings={handleShowSettings}
121
- onShowWords={handleShowWords}
122
- />
197
+ {/* eslint-disable-next-line react/no-danger */}
198
+ <script dangerouslySetInnerHTML={{ __html: WEB_APPLICATION_JSON_LD }} type="application/ld+json" />
199
+
200
+ {mountedModals.menu && (
201
+ <MenuModal
202
+ isOpen={modals.menu}
203
+ onClose={handleCloseMenu}
204
+ onShowDictionary={handleShowDictionary}
205
+ onShowRemainingTiles={handleShowRemainingTiles}
206
+ onShowSettings={handleShowSettings}
207
+ onShowWords={handleShowWords}
208
+ />
209
+ )}
123
210
 
124
- <SettingsModal isOpen={modals.settings} onClose={handleCloseSettings} />
211
+ {mountedModals.settings && <SettingsModal isOpen={modals.settings} onClose={handleCloseSettings} />}
125
212
 
126
- <KeyMapModal isOpen={modals.keyMap} onClose={handleCloseKeyMap} />
213
+ {mountedModals.keyMap && <KeyMapModal isOpen={modals.keyMap} onClose={handleCloseKeyMap} />}
127
214
 
128
- <WordsModal isOpen={modals.words} onClose={handleCloseWords} />
215
+ {mountedModals.words && <WordsModal isOpen={modals.words} onClose={handleCloseWords} />}
129
216
 
130
- {config.supportsRemainingTiles && (
217
+ {config.supportsRemainingTiles && mountedModals.remainingTiles && (
131
218
  <RemainingTilesModal isOpen={modals.remainingTiles} onClose={handleCloseRemainingTiles} />
132
219
  )}
133
220
 
134
- <ResultsModal isOpen={modals.results} onClose={handleCloseResults} />
221
+ {mountedModals.results && <ResultsModal isOpen={modals.results} onClose={handleCloseResults} />}
135
222
 
136
- <DictionaryModal isOpen={modals.dictionary} onClose={handleCloseDictionary} />
223
+ {mountedModals.dictionary && <DictionaryModal isOpen={modals.dictionary} onClose={handleCloseDictionary} />}
137
224
  </>
138
225
  );
139
226
  };
@@ -1,5 +1,16 @@
1
- 'use-client';
1
+ import Head from 'next/head';
2
+ import { type FunctionComponent } from 'react';
2
3
 
3
- import { NotFound } from '@/components';
4
+ import { NotFound } from '@/components/NotFound';
4
5
 
5
- export default NotFound;
6
+ const NotFoundPage: FunctionComponent = () => (
7
+ <>
8
+ <Head>
9
+ <title>Page not found - Scrabble Solver</title>
10
+ </Head>
11
+
12
+ <NotFound />
13
+ </>
14
+ );
15
+
16
+ export default NotFoundPage;
@@ -1,5 +1,4 @@
1
1
  import variables from '@/styles/variables.module.scss';
2
- import { ResultColumnId } from '@/types';
3
2
 
4
3
  export const BREAKPOINTS = {
5
4
  xs: Number(variables.breakpointXs),
@@ -9,97 +8,59 @@ export const BREAKPOINTS = {
9
8
  xl: Number(variables.breakpointXl),
10
9
  };
11
10
 
12
- export const TRANSITION = 'var(--transition)';
13
-
14
11
  export const GITHUB_PROJECT_URL = 'https://github.com/kamilmielnik/scrabble-solver';
15
12
 
16
- export const TRANSITION_DURATION = Number(variables.transitionDuration);
13
+ export const NPM_PACKAGE_URL = 'https://www.npmjs.com/package/scrabble-solver';
14
+
15
+ export const SITE_URL = 'https://scrabble-solver.org';
16
+
17
+ export const SITE_DESCRIPTION =
18
+ 'Free, open-source board solver and word finder for Scrabble, Letter League, and Literaki. Enter your letters and board to get the highest-scoring words.';
19
+
20
+ // Must match the class name in global.scss
21
+ export const CONFIG_PENDING_CLASS = 'config-pending';
22
+
17
23
  export const TRANSITION_DURATION_LONG = Number(variables.transitionDurationLong);
18
24
 
19
- export const COLOR_BLUE = variables.colorBlue;
20
- export const COLOR_GREEN = variables.colorGreen;
21
- export const COLOR_RED = variables.colorRed;
22
- export const COLOR_YELLOW = variables.colorYellow;
23
-
24
- export const COLOR_BONUS_CHARACTER_1 = variables.colorYellowLight;
25
- export const COLOR_BONUS_CHARACTER_2 = variables.colorGreenLight;
26
- export const COLOR_BONUS_CHARACTER_3 = variables.colorBlueLight;
27
- export const COLOR_BONUS_CHARACTER_5 = variables.colorRedLight;
28
- export const COLOR_BONUS_CHARACTER_MULTIPLIER_2 = variables.colorBonusCharacterMultiplier2;
29
- export const COLOR_BONUS_CHARACTER_MULTIPLIER_3 = variables.colorBonusCharacterMultiplier3;
30
- export const COLOR_BONUS_CHARACTER_MULTIPLIER_4 = variables.colorBonusCharacterMultiplier4;
31
- export const COLOR_BONUS_START = variables.colorVioletLight;
32
- export const COLOR_BONUS_WORD_MULTIPLIER_2 = variables.colorBonusWordMultiplier2;
33
- export const COLOR_BONUS_WORD_MULTIPLIER_3 = variables.colorBonusWordMultiplier3;
34
- export const COLOR_BONUS_WORD_MULTIPLIER_4 = variables.colorBonusWordMultiplier4;
35
- export const COLOR_FILTERED = variables.colorForegroundSecondary;
25
+ export const COLOR_BLUE = 'var(--color--blue)';
26
+ export const COLOR_GREEN = 'var(--color--green)';
27
+ export const COLOR_RED = 'var(--color--red)';
28
+ export const COLOR_YELLOW = 'var(--color--yellow)';
29
+
36
30
  export const COLOR_BACKGROUND = variables.colorBackground;
31
+ export const COLOR_BONUS_START = variables.colorVioletLight;
37
32
 
38
33
  export const COLOR_BONUS_CHARACTER: Record<number, string> = {
39
- 1: COLOR_BONUS_CHARACTER_1,
40
- 2: COLOR_BONUS_CHARACTER_2,
41
- 3: COLOR_BONUS_CHARACTER_3,
42
- 5: COLOR_BONUS_CHARACTER_5,
34
+ 1: variables.colorYellowLight,
35
+ 2: variables.colorGreenLight,
36
+ 3: variables.colorBlueLight,
37
+ 5: variables.colorRedLight,
43
38
  };
44
39
 
45
40
  export const COLOR_BONUS_CHARACTER_MULTIPLIER: Record<number, string> = {
46
- 2: COLOR_BONUS_CHARACTER_MULTIPLIER_2,
47
- 3: COLOR_BONUS_CHARACTER_MULTIPLIER_3,
48
- 4: COLOR_BONUS_CHARACTER_MULTIPLIER_4,
41
+ 2: variables.colorBonusCharacterMultiplier2,
42
+ 3: variables.colorBonusCharacterMultiplier3,
43
+ 4: variables.colorBonusCharacterMultiplier4,
49
44
  };
50
45
 
51
46
  export const COLOR_BONUS_WORD: Record<number, string> = {
52
- 2: COLOR_BONUS_WORD_MULTIPLIER_2,
53
- 3: COLOR_BONUS_WORD_MULTIPLIER_3,
54
- 4: COLOR_BONUS_WORD_MULTIPLIER_4,
47
+ 2: variables.colorBonusWordMultiplier2,
48
+ 3: variables.colorBonusWordMultiplier3,
49
+ 4: variables.colorBonusWordMultiplier4,
55
50
  };
56
51
 
57
- export const SPACING_XS = Number(variables.spacingXs);
58
- export const SPACING_S = Number(variables.spacingS);
59
- export const SPACING_M = Number(variables.spacingM);
60
- export const SPACING_L = Number(variables.spacingL);
61
- export const SPACING_XL = Number(variables.spacingXl);
62
-
63
- export const COMPONENTS_SPACING = SPACING_XL;
64
- export const COMPONENTS_SPACING_SMALL = SPACING_L;
65
-
66
- export const RACK_TILE_SIZE_MAX = 80;
67
-
68
52
  export const BOARD_CELL_ACTIONS_OFFSET = 3;
69
- export const BOARD_CELL_BORDER_WIDTH = 1;
70
- export const BOARD_TILE_FONT_SIZE_MIN = 14;
71
- export const BOARD_TILE_FONT_SIZE_POINTS_MIN = 10;
72
- export const BOARD_TILE_SIZE_MAX = RACK_TILE_SIZE_MAX;
73
53
 
74
- export const BORDER_COLOR = variables.borderColor;
75
54
  export const BORDER_COLOR_LIGHT = variables.borderColorLight;
76
- export const BORDER_RADIUS = Number(variables.borderRadius);
77
55
  export const BORDER_WIDTH = Number(variables.borderWidth);
78
56
 
79
- export const BUTTON_ICON_SIZE = Number(variables.buttonIconSize);
80
- export const BUTTON_PADDING_VERTICAL = SPACING_M;
81
- export const BUTTON_HEIGHT = BUTTON_ICON_SIZE + 2 * BUTTON_PADDING_VERTICAL + 2 * BORDER_WIDTH;
82
-
83
- export const DICTIONARY_HEIGHT = Number(variables.dictionaryHeight);
84
- export const DICTIONARY_HEIGHT_MOBILE = Number(variables.dictionaryHeightMobile);
85
-
86
- export const LOGO_ASPECT_RATIO = 682 / 166;
87
- export const LOGO_HEIGHT = Number(variables.logoHeight);
88
- export const LOGO_HEIGHT_SMALL = Number(variables.logoHeightSmall);
57
+ export const LOGO_HEIGHT = 166;
89
58
  export const LOGO_SRC = '/logo.svg';
59
+ export const LOGO_WIDTH = 682;
90
60
 
91
- export const MODAL_WIDTH = Number(variables.modalWidth);
92
- export const MODAL_HEADER_HEIGHT = 45;
93
-
94
- export const NAV_PADDING = SPACING_L;
95
-
96
- export const TILE_SIZE = 80;
97
-
98
- export const TOOLTIP_DURATION = TRANSITION_DURATION;
61
+ export const TOOLTIP_DURATION = Number(variables.transitionDuration);
99
62
 
100
63
  export const PLAIN_TILES_COLOR_DEFAULT = COLOR_GREEN;
101
- export const PLAIN_TILES_PADDING_HORIZONTAL = 0;
102
- export const PLAIN_TILES_PADDING_VERTICAL = 0;
103
64
  export const PLAIN_TILES_POINTS_COLORS: Record<number, string> = {
104
65
  1: COLOR_YELLOW,
105
66
  2: COLOR_GREEN,
@@ -113,31 +74,8 @@ export const PLAIN_TILES_POINTS_COLORS: Record<number, string> = {
113
74
  10: COLOR_RED,
114
75
  };
115
76
  export const PLAIN_TILES_TILE_MARGIN = 6;
116
- export const PLAIN_TILES_TILE_MAX_ROTATE = 0;
117
- export const PLAIN_TILES_TILE_MAX_SCATTER = 0;
118
77
  export const PLAIN_TILES_TILE_SIZE = 80;
119
- export const PLAIN_TILES_VERSION_TILE_COLOR = COLOR_GREEN;
120
- export const PLAIN_TILES_VERSION_TILE_SIZE = TILE_SIZE;
121
-
122
- export const PROGRESS_COLOR_VALUE = 'var(--color--violet--light)';
123
- export const PROGRESS_COLOR_BACKGROUND = 'var(--color--inactive)';
124
-
125
- export const REMAINING_TILES_TILE_SIZE = 50;
126
-
127
- export const RESULTS_COLUMN_WIDTH: Record<ResultColumnId, number | undefined> = {
128
- [ResultColumnId.BlanksCount]: 55,
129
- [ResultColumnId.ConsonantsCount]: 55,
130
- [ResultColumnId.Coordinates]: 55,
131
- [ResultColumnId.Points]: 80,
132
- [ResultColumnId.TilesCount]: 55,
133
- [ResultColumnId.VowelsCount]: 55,
134
- [ResultColumnId.Word]: undefined,
135
- [ResultColumnId.WordsCount]: 55,
136
- };
137
- export const RESULTS_ITEM_HEIGHT = Number(variables.resultsItemHeight);
138
78
 
139
- export const RESULTS_HEADER_HEIGHT = RESULTS_ITEM_HEIGHT;
140
-
141
- export const SOLVER_COLUMN_WIDTH = Number(variables.solverColumnWidth);
79
+ export const RESULTS_ITEM_HEIGHT = Number(variables.resultsItemHeight);
142
80
 
143
- export const TEXT_INPUT_HEIGHT = Number(variables.textInputHeight);
81
+ export const RESULTS_OVERSCAN_COUNT = 10;
@@ -1,9 +1,12 @@
1
- import { Workbox } from 'workbox-window';
1
+ import { waitForIdle } from '@/lib/waitForIdleOrFirstIntent';
2
2
 
3
3
  export const registerServiceWorker = async () => {
4
4
  if (!globalThis.navigator || !('serviceWorker' in globalThis.navigator)) {
5
5
  return;
6
6
  }
7
7
 
8
- await new Workbox('/service-worker.js').register({ immediate: true });
8
+ // Registration waits for load+idle so the install's precache downloads never compete with page resources
9
+ await waitForIdle();
10
+ const { Workbox } = await import('workbox-window');
11
+ await new Workbox('/service-worker.js').register();
9
12
  };
@@ -1,5 +1,5 @@
1
1
  import { createAction } from '@reduxjs/toolkit';
2
2
 
3
- export const initialize = createAction('initialize');
3
+ export const initialize = createAction<{ version: string }>('initialize');
4
4
 
5
5
  export const reset = createAction('reset');
@@ -0,0 +1,4 @@
1
+ export * from './initialState';
2
+ export * from './selectors';
3
+ export * from './slice';
4
+ export * from './types';
@@ -0,0 +1,6 @@
1
+ import type { AppState } from './types';
2
+
3
+ export const appInitialState: AppState = {
4
+ isHydrated: false,
5
+ version: '',
6
+ };
@@ -0,0 +1,5 @@
1
+ import type { RootState } from '../types';
2
+
3
+ export const selectIsHydrated = (state: RootState): boolean => state.app.isHydrated;
4
+
5
+ export const selectVersion = (state: RootState): string => state.app.version;
@@ -0,0 +1,20 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+
3
+ import { initialize } from '../actions';
4
+
5
+ import { appInitialState } from './initialState';
6
+
7
+ export const appSlice = createSlice({
8
+ initialState: appInitialState,
9
+ name: 'app',
10
+ reducers: {
11
+ hydrated: (state) => {
12
+ state.isHydrated = true;
13
+ },
14
+ },
15
+ extraReducers: (builder) => {
16
+ builder.addCase(initialize, (state, action) => {
17
+ state.version = action.payload.version;
18
+ });
19
+ },
20
+ });
@@ -0,0 +1,4 @@
1
+ export interface AppState {
2
+ isHydrated: boolean;
3
+ version: string;
4
+ }
@@ -1,7 +1,6 @@
1
1
  import { getConfig } from '@scrabble-solver/configs';
2
2
  import { Board } from '@scrabble-solver/types';
3
3
 
4
- import { localStorage } from '../localStorage';
5
4
  import { settingsInitialState } from '../settings';
6
5
 
7
6
  import type { BoardState } from './types';
@@ -9,7 +8,7 @@ import type { BoardState } from './types';
9
8
  const { game, locale } = settingsInitialState;
10
9
  const { boardHeight, boardWidth } = getConfig(game, locale);
11
10
 
12
- export const boardInitialState: BoardState = localStorage.getBoard() ?? Board.create(boardWidth, boardHeight);
11
+ export const boardInitialState: BoardState = Board.create(boardWidth, boardHeight);
13
12
 
14
13
  // const createOxyphenbutazone = () => {
15
14
  // // Tiles: oypbaze
@@ -0,0 +1,4 @@
1
+ export * from './initialState';
2
+ export * from './selectors';
3
+ export * from './slice';
4
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import type { I18nState } from './types';
2
+
3
+ export const i18nInitialState: I18nState = {
4
+ translations: {},
5
+ };
@@ -0,0 +1,3 @@
1
+ import type { RootState } from '../types';
2
+
3
+ export const selectLoadedTranslations = (state: RootState) => state.i18n.translations;
@@ -0,0 +1,16 @@
1
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
2
+ import { type Locale } from '@scrabble-solver/types';
3
+
4
+ import type { Translations } from '@/types';
5
+
6
+ import { i18nInitialState } from './initialState';
7
+
8
+ export const i18nSlice = createSlice({
9
+ initialState: i18nInitialState,
10
+ name: 'i18n',
11
+ reducers: {
12
+ loaded: (state, action: PayloadAction<{ locale: Locale; translations: Translations }>) => {
13
+ state.translations[action.payload.locale] = action.payload.translations;
14
+ },
15
+ },
16
+ });
@@ -0,0 +1,7 @@
1
+ import { type Locale } from '@scrabble-solver/types';
2
+
3
+ import type { Translations } from '@/types';
4
+
5
+ export interface I18nState {
6
+ translations: Partial<Record<Locale, Translations>>;
7
+ }
@@ -1,8 +1,10 @@
1
1
  export * from './actions';
2
+ export * from './app';
2
3
  export * from './board';
3
4
  export * from './cellFilters';
4
5
  export * from './dictionary';
5
6
  export * from './hoveredTile';
7
+ export * from './i18n';
6
8
  export * from './localStorage';
7
9
  export * from './rack';
8
10
  export * from './results';
@@ -1,7 +1,7 @@
1
1
  import { BLANK } from '@scrabble-solver/constants';
2
2
  import { type Board, type Config } from '@scrabble-solver/types';
3
3
 
4
- import { createKeyComparator } from '@/lib';
4
+ import { createKeyComparator } from '@/lib/createKeyComparator';
5
5
  import { type RemainingTile } from '@/types';
6
6
 
7
7
  export const getRemainingTiles = (