@scrabble-solver/scrabble-solver 2.17.0 → 2.18.0

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 (328) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +22 -20
  3. package/.next/cache/.previewinfo +1 -1
  4. package/.next/cache/.rscinfo +1 -1
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  11. package/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  14. package/.next/diagnostics/framework.json +1 -1
  15. package/.next/dynamic-css-manifest.json +8 -1
  16. package/.next/next-minimal-server.js.nft.json +1 -1
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +4 -4
  19. package/.next/react-loadable-manifest.json +176 -1
  20. package/.next/routes-manifest.json +1 -1
  21. package/.next/server/chunks/309.js +1 -0
  22. package/.next/server/chunks/{785.js → 316.js} +1 -1
  23. package/.next/server/chunks/344.js +1 -0
  24. package/.next/server/chunks/371.js +1 -0
  25. package/.next/server/chunks/465.js +1 -0
  26. package/.next/server/chunks/546.js +1 -0
  27. package/.next/server/chunks/623.js +1 -0
  28. package/.next/server/chunks/624.js +1 -0
  29. package/.next/server/chunks/641.js +1 -0
  30. package/.next/server/chunks/671.js +1 -1
  31. package/.next/server/chunks/692.js +1 -0
  32. package/.next/server/chunks/705.js +1 -0
  33. package/.next/server/chunks/712.js +1 -1
  34. package/.next/server/chunks/846.js +1 -0
  35. package/.next/server/chunks/853.js +15 -0
  36. package/.next/server/chunks/886.js +1 -0
  37. package/.next/server/chunks/916.js +1 -0
  38. package/.next/server/chunks/953.js +1 -0
  39. package/.next/server/chunks/961.js +1 -0
  40. package/.next/server/chunks/968.js +1 -0
  41. package/.next/server/dynamic-css-manifest.js +1 -1
  42. package/.next/server/middleware-build-manifest.js +1 -1
  43. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  44. package/.next/server/pages/404.html +28 -1
  45. package/.next/server/pages/500.html +28 -1
  46. package/.next/server/pages/_app.js +1 -1
  47. package/.next/server/pages/_app.js.nft.json +1 -1
  48. package/.next/server/pages/_document.js +28 -1
  49. package/.next/server/pages/_document.js.nft.json +1 -1
  50. package/.next/server/pages/_error.js +28 -1
  51. package/.next/server/pages/_error.js.nft.json +1 -1
  52. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  53. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  54. package/.next/server/pages/api/solve.js +1 -1
  55. package/.next/server/pages/api/solve.js.nft.json +1 -1
  56. package/.next/server/pages/api/verify.js.nft.json +1 -1
  57. package/.next/server/pages/api/visit.js.nft.json +1 -1
  58. package/.next/server/pages/index.html +28 -1
  59. package/.next/server/pages/index.js +30 -1
  60. package/.next/server/pages/index.js.nft.json +1 -1
  61. package/.next/server/pages/index.json +1 -1
  62. package/.next/server/pages/not-found.html +28 -1
  63. package/.next/server/pages/not-found.js.nft.json +1 -1
  64. package/.next/server/pages-manifest.json +1 -1
  65. package/.next/server/webpack-api-runtime.js +1 -1
  66. package/.next/server/webpack-runtime.js +1 -1
  67. package/.next/static/FL82obwzFHRzvV6z76I-U/_buildManifest.js +1 -0
  68. package/.next/static/chunks/101-829ab8a8d310e7a0.js +3 -0
  69. package/.next/static/chunks/106.e414e88694add27f.js +1 -0
  70. package/.next/static/chunks/127.125d896a19061d71.js +1 -0
  71. package/.next/static/chunks/18.8b91e137d239e15c.js +1 -0
  72. package/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
  73. package/.next/static/chunks/307.67f52f13e3d60d4e.js +1 -0
  74. package/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
  75. package/.next/static/chunks/363.01eed3233f92b6a4.js +1 -0
  76. package/.next/static/chunks/422.1559ae6756912ca5.js +1 -0
  77. package/.next/static/chunks/541.dcd9e8b804f88da5.js +1 -0
  78. package/.next/static/chunks/631.98d42abad0a60af6.js +1 -0
  79. package/.next/static/chunks/663.d2c9ca36b26695b8.js +1 -0
  80. package/.next/static/chunks/711.ba7b8f900dfd40d0.js +1 -0
  81. package/.next/static/chunks/744.b06c9a3de4a1d877.js +1 -0
  82. package/.next/static/chunks/820.97b82f751ca49579.js +1 -0
  83. package/.next/static/chunks/865.a9b563a45848a83c.js +1 -0
  84. package/.next/static/chunks/912.ff8037ba9d4db5c8.js +1 -0
  85. package/.next/static/chunks/939.19b33a3d3c351d73.js +1 -0
  86. package/.next/static/chunks/990.61296e5870b91f11.js +1 -0
  87. package/.next/static/chunks/framework-626cbd48ac5aca57.js +1 -0
  88. package/.next/static/chunks/main-b92e08f3fc96dd8d.js +5 -0
  89. package/.next/static/chunks/pages/_app-ba5d628e81a15461.js +1 -0
  90. package/.next/static/chunks/pages/{_error-afeda7c5231b6c3c.js → _error-f0290a98eb3ed0ec.js} +1 -1
  91. package/.next/static/chunks/pages/index-5f9fa6ac3cd3d97b.js +1 -0
  92. package/.next/static/chunks/pages/not-found-23d61fd3ed791cad.js +1 -0
  93. package/.next/static/chunks/webpack-e34c3ae12275e978.js +1 -0
  94. package/.next/static/css/2d03f0da2c678dfc.css +1 -0
  95. package/.next/static/css/7b6724d2df70f48f.css +1 -0
  96. package/.next/static/css/abc2425d3ba607bc.css +1 -0
  97. package/.next/static/css/b736be3bd02b5d99.css +1 -0
  98. package/.next/static/css/b74cd7de82dabcdd.css +1 -0
  99. package/.next/static/css/c403ce739a9c0d83.css +1 -0
  100. package/.next/static/css/ca311e6c030663d4.css +1 -0
  101. package/.next/static/css/dee878eb64099d92.css +1 -0
  102. package/.next/static/css/fbfdce70731d0d2b.css +1 -0
  103. package/.next/trace +21 -21
  104. package/.next/trace-build +1 -1
  105. package/empty-module.js +2 -0
  106. package/next.config.js +13 -2
  107. package/package.json +34 -15
  108. package/public/llms.txt +11 -0
  109. package/public/robots.txt +4 -0
  110. package/scripts/generate-scss-types.ts +112 -0
  111. package/src/@types/scss.d.ts +6 -9
  112. package/src/@types/svg.d.ts +1 -1
  113. package/src/components/Alert/Alert.module.scss.d.ts +13 -0
  114. package/src/components/Alert/Alert.tsx +4 -1
  115. package/src/components/Badge/Badge.module.scss.d.ts +6 -0
  116. package/src/components/Board/Board.module.scss +10 -0
  117. package/src/components/Board/Board.module.scss.d.ts +17 -0
  118. package/src/components/Board/Board.tsx +14 -26
  119. package/src/components/Board/BoardPure.tsx +54 -49
  120. package/src/components/Board/components/Actions/Actions.module.scss +18 -18
  121. package/src/components/Board/components/Actions/Actions.module.scss.d.ts +7 -0
  122. package/src/components/Board/components/Actions/Actions.tsx +6 -3
  123. package/src/components/Board/components/Actions/lib.ts +3 -1
  124. package/src/components/Board/components/Cell/Cell.module.scss.d.ts +13 -0
  125. package/src/components/Board/components/Cell/Cell.tsx +28 -33
  126. package/src/components/Board/components/InputPrompt/InputPrompt.module.scss.d.ts +10 -0
  127. package/src/components/Board/components/InputPrompt/InputPrompt.tsx +1 -1
  128. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss.d.ts +8 -0
  129. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
  130. package/src/components/Board/getBoardBackground.ts +175 -0
  131. package/src/components/Board/hooks/index.ts +0 -1
  132. package/src/components/Board/hooks/useBoardStyle.ts +10 -20
  133. package/src/components/Board/hooks/useFloatingActions.ts +3 -6
  134. package/src/components/Board/hooks/useGrid.ts +6 -3
  135. package/src/components/Board/selectors.ts +1 -1
  136. package/src/components/Button/Button.module.scss.d.ts +12 -0
  137. package/src/components/Dictionary/Dictionary.module.scss +1 -0
  138. package/src/components/Dictionary/Dictionary.module.scss.d.ts +14 -0
  139. package/src/components/Dictionary/Dictionary.tsx +1 -3
  140. package/src/components/DictionaryInput/DictionaryInput.module.scss.d.ts +7 -0
  141. package/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
  142. package/src/components/EmptyState/EmptyState.module.scss.d.ts +7 -0
  143. package/src/components/EmptyState/EmptyState.tsx +1 -1
  144. package/src/components/IconButton/IconButton.module.scss.d.ts +8 -0
  145. package/src/components/Key/Key.module.scss.d.ts +6 -0
  146. package/src/components/Keys/Arrows/Arrows.module.scss.d.ts +11 -0
  147. package/src/components/Keys/Arrows/Arrows.tsx +4 -1
  148. package/src/components/Keys/index.tsx +1 -1
  149. package/src/components/Loading/Loading.module.scss.d.ts +9 -0
  150. package/src/components/Loading/Loading.tsx +1 -1
  151. package/src/components/Logo/Logo.tsx +4 -9
  152. package/src/components/Logo/LogoBlueprint.tsx +1 -0
  153. package/src/components/Modal/Modal.module.scss.d.ts +15 -0
  154. package/src/components/Modal/Modal.tsx +16 -3
  155. package/src/components/Modal/components/Section/Section.module.scss.d.ts +7 -0
  156. package/src/components/NavButtons/NavButtons.module.scss +20 -0
  157. package/src/components/NavButtons/NavButtons.module.scss.d.ts +13 -0
  158. package/src/components/NavButtons/NavButtons.tsx +29 -45
  159. package/src/components/NotFound/NotFound.module.scss.d.ts +9 -0
  160. package/src/components/NotFound/NotFound.tsx +1 -1
  161. package/src/components/PlainTiles/PlainTiles.module.scss.d.ts +11 -0
  162. package/src/components/PlainTiles/PlainTiles.tsx +0 -1
  163. package/src/components/PlainTiles/Tile.tsx +3 -4
  164. package/src/components/PlainTiles/lib.ts +4 -19
  165. package/src/components/PlainTiles/types.ts +0 -1
  166. package/src/components/Progress/Progress.module.scss +7 -0
  167. package/src/components/Progress/Progress.module.scss.d.ts +6 -0
  168. package/src/components/Progress/Progress.tsx +2 -11
  169. package/src/components/Rack/Rack.module.scss +3 -0
  170. package/src/components/Rack/Rack.module.scss.d.ts +10 -0
  171. package/src/components/Rack/Rack.tsx +8 -11
  172. package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +2 -0
  173. package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss.d.ts +7 -0
  174. package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -13
  175. package/src/components/Rack/components/InputPrompt/lib.ts +1 -1
  176. package/src/components/Rack/components/RackTile/RackTile.module.scss.d.ts +6 -0
  177. package/src/components/Rack/components/RackTile/RackTile.tsx +5 -4
  178. package/src/components/Rack/selectors.ts +1 -1
  179. package/src/components/Radio/Radio.module.scss.d.ts +12 -0
  180. package/src/components/Results/Cell.tsx +3 -12
  181. package/src/components/Results/Header.tsx +9 -18
  182. package/src/components/Results/HeaderButton.tsx +4 -5
  183. package/src/components/Results/Result.tsx +4 -12
  184. package/src/components/Results/Results.module.scss +4 -6
  185. package/src/components/Results/Results.module.scss.d.ts +27 -0
  186. package/src/components/Results/Results.tsx +4 -3
  187. package/src/components/Results/SolveButton.tsx +1 -1
  188. package/src/components/ResultsInput/ResultsInput.module.scss.d.ts +7 -0
  189. package/src/components/ResultsInput/ResultsInput.tsx +1 -1
  190. package/src/components/Solver/Solver.module.scss +6 -5
  191. package/src/components/Solver/Solver.module.scss.d.ts +20 -0
  192. package/src/components/Solver/Solver.tsx +19 -21
  193. package/src/components/Solver/components/InsertButton/InsertButton.module.scss.d.ts +7 -0
  194. package/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
  195. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +18 -0
  196. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +3 -1
  197. package/src/components/Spinner/Spinner.module.scss.d.ts +10 -0
  198. package/src/components/Tile/Tile.module.scss +3 -0
  199. package/src/components/Tile/Tile.module.scss.d.ts +19 -0
  200. package/src/components/Tile/Tile.tsx +5 -14
  201. package/src/components/Tile/TilePure.tsx +2 -12
  202. package/src/components/Tooltip/Tooltip.module.scss.d.ts +7 -0
  203. package/src/components/Tooltip/Tooltip.tsx +14 -3
  204. package/src/components/Tooltip/useTooltip.ts +1 -0
  205. package/src/hooks/useColumns.ts +4 -7
  206. package/src/hooks/useDeferredRender.ts +20 -0
  207. package/src/hooks/useDirection.ts +4 -3
  208. package/src/hooks/useLanguage.ts +2 -2
  209. package/src/hooks/useLocalStorage.ts +32 -8
  210. package/src/hooks/useMedia.ts +26 -26
  211. package/src/i18n/constants.ts +2 -15
  212. package/src/i18n/i18n.ts +18 -17
  213. package/src/i18n/languages/english.json +0 -1
  214. package/src/i18n/languages/french.json +0 -1
  215. package/src/i18n/languages/german.json +0 -1
  216. package/src/i18n/languages/persian.json +0 -1
  217. package/src/i18n/languages/polish.json +0 -1
  218. package/src/i18n/languages/romanian.json +0 -1
  219. package/src/i18n/languages/spanish.json +0 -1
  220. package/src/i18n/languages/turkish.json +0 -1
  221. package/src/i18n/localeIcons.ts +24 -0
  222. package/src/lib/localeTransliterate.test.ts +5 -0
  223. package/src/lib/localeTransliterate.ts +26 -6
  224. package/src/lib/waitForIdleOrFirstIntent.ts +33 -0
  225. package/src/modals/DictionaryModal/DictionaryModal.module.scss.d.ts +8 -0
  226. package/src/modals/DictionaryModal/DictionaryModal.tsx +3 -1
  227. package/src/modals/KeyMapModal/KeyMapModal.tsx +3 -1
  228. package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss.d.ts +11 -0
  229. package/src/modals/MenuModal/MenuModal.module.scss +5 -0
  230. package/src/modals/MenuModal/MenuModal.module.scss.d.ts +10 -0
  231. package/src/modals/MenuModal/MenuModal.tsx +16 -5
  232. package/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +4 -1
  233. package/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss.d.ts +9 -0
  234. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -6
  235. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  236. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss.d.ts +12 -0
  237. package/src/modals/RemainingTilesModal/components/Character/Character.tsx +5 -12
  238. package/src/modals/ResultsModal/ResultsModal.module.scss.d.ts +8 -0
  239. package/src/modals/ResultsModal/ResultsModal.tsx +8 -4
  240. package/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  241. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.module.scss.d.ts +7 -0
  242. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
  243. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss.d.ts +7 -0
  244. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
  245. package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss.d.ts +7 -0
  246. package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +1 -1
  247. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss.d.ts +7 -0
  248. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
  249. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss.d.ts +8 -0
  250. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +24 -21
  251. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss.d.ts +7 -0
  252. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +1 -1
  253. package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.module.scss.d.ts +7 -0
  254. package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -5
  255. package/src/modals/WordsModal/WordsModal.module.scss.d.ts +11 -0
  256. package/src/modals/WordsModal/WordsModal.tsx +4 -2
  257. package/src/modals/preload.ts +24 -0
  258. package/src/pages/_app.tsx +5 -8
  259. package/src/pages/_document.tsx +91 -5
  260. package/src/pages/api/solve.ts +1 -1
  261. package/src/pages/index.module.scss +1 -0
  262. package/src/pages/index.module.scss.d.ts +11 -0
  263. package/src/pages/index.tsx +98 -38
  264. package/src/pages/not-found.tsx +1 -1
  265. package/src/parameters/index.ts +24 -93
  266. package/src/serviceWorkerManager.ts +5 -2
  267. package/src/state/actions.ts +1 -1
  268. package/src/state/app/index.ts +4 -0
  269. package/src/state/app/initialState.ts +6 -0
  270. package/src/state/app/selectors.ts +5 -0
  271. package/src/state/app/slice.ts +20 -0
  272. package/src/state/app/types.ts +4 -0
  273. package/src/state/board/initialState.ts +1 -2
  274. package/src/state/i18n/index.ts +4 -0
  275. package/src/state/i18n/initialState.ts +5 -0
  276. package/src/state/i18n/selectors.ts +3 -0
  277. package/src/state/i18n/slice.ts +16 -0
  278. package/src/state/i18n/types.ts +7 -0
  279. package/src/state/index.ts +2 -0
  280. package/src/state/lib.ts +1 -1
  281. package/src/state/localStorage.ts +86 -29
  282. package/src/state/rack/initialState.ts +1 -2
  283. package/src/state/rack/slice.ts +4 -1
  284. package/src/state/results/lib.ts +5 -5
  285. package/src/state/results/selectors.ts +1 -1
  286. package/src/state/sagas.ts +125 -9
  287. package/src/state/selectors.ts +1 -1
  288. package/src/state/settings/initialState.ts +7 -10
  289. package/src/state/settings/selectors.ts +7 -2
  290. package/src/state/settings/slice.ts +5 -2
  291. package/src/state/store.ts +4 -0
  292. package/src/state/types.ts +4 -0
  293. package/src/styles/_tokens.scss +1 -0
  294. package/src/styles/global.scss +9 -0
  295. package/src/styles/variables.module.scss +0 -21
  296. package/src/styles/variables.module.scss.d.ts +27 -0
  297. package/src/styles/variables.scss +46 -2
  298. package/src/types/index.ts +1 -2
  299. package/tsconfig.tsbuildinfo +1 -1
  300. package/.next/server/chunks/129.js +0 -1
  301. package/.next/server/chunks/318.js +0 -3
  302. package/.next/server/chunks/791.js +0 -15
  303. package/.next/static/OLE8_ZInlGH7ENU0NIOpf/_buildManifest.js +0 -1
  304. package/.next/static/chunks/830.661816bf0de8c2f1.js +0 -1
  305. package/.next/static/chunks/framework-3cf15b8041c59dd3.js +0 -1
  306. package/.next/static/chunks/main-5b999f3f24ebfa84.js +0 -5
  307. package/.next/static/chunks/pages/_app-7024e7a27ffbb2fa.js +0 -7
  308. package/.next/static/chunks/pages/index-8b398b0a5d870773.js +0 -1
  309. package/.next/static/chunks/pages/not-found-9e076427bad69d90.js +0 -1
  310. package/.next/static/chunks/webpack-3943d624bbb47a64.js +0 -1
  311. package/.next/static/css/a78e04fe95452380.css +0 -1
  312. package/.next/static/css/aedeaada218d7280.css +0 -2
  313. package/src/app-layout/AppLayoutContext.tsx +0 -25
  314. package/src/app-layout/index.ts +0 -1
  315. package/src/app-layout/useAppLayoutValue.ts +0 -112
  316. package/src/components/Board/hooks/useBackgroundImage.tsx +0 -279
  317. package/src/components/index.ts +0 -26
  318. package/src/hooks/index.ts +0 -12
  319. package/src/hooks/useOnWindowResize.ts +0 -11
  320. package/src/hooks/useViewportSize.ts +0 -17
  321. package/src/i18n/index.ts +0 -2
  322. package/src/icons/Flag.svg +0 -4
  323. package/src/icons/Star.svg +0 -4
  324. package/src/icons/index.ts +0 -49
  325. package/src/lib/getTileSizes.ts +0 -13
  326. package/src/lib/index.ts +0 -24
  327. package/src/modals/index.ts +0 -7
  328. /package/.next/static/{OLE8_ZInlGH7ENU0NIOpf → FL82obwzFHRzvV6z76I-U}/_ssgManifest.js +0 -0
@@ -1,4 +1,11 @@
1
- import { type Cell as CellModel, type ShowCoordinates } from '@scrabble-solver/types';
1
+ import { BLANK, EMPTY_CELL } from '@scrabble-solver/constants';
2
+ import {
3
+ type Cell as CellModel,
4
+ type Config,
5
+ type Locale,
6
+ type ShowCoordinates,
7
+ type TextDirection,
8
+ } from '@scrabble-solver/types';
2
9
  import classNames from 'classnames';
3
10
  import {
4
11
  type CSSProperties,
@@ -12,10 +19,10 @@ import {
12
19
  memo,
13
20
  } from 'react';
14
21
 
15
- import { Ban, FlagFill } from '@/icons';
16
- import { getCoordinate } from '@/lib';
17
- import { BORDER_WIDTH } from '@/parameters';
18
- import { type CellFilter } from '@/types';
22
+ import Ban from '@/icons/Ban.svg';
23
+ import FlagFill from '@/icons/FlagFill.svg';
24
+ import { getCoordinate } from '@/lib/getCoordinate';
25
+ import { type CellFilter, type Translate } from '@/types';
19
26
 
20
27
  import styles from './Board.module.scss';
21
28
  import { Cell } from './components';
@@ -23,15 +30,16 @@ import { Cell } from './components';
23
30
  interface Props {
24
31
  className?: string;
25
32
  cellFilters: CellFilter[];
26
- cellSize: number;
27
- coordinatesFontSize: number;
28
- coordinatesSize: number;
29
- direction: 'ltr' | 'rtl';
33
+ config: Config;
34
+ direction: TextDirection;
35
+ hoveredCharacter: string | null;
30
36
  inputRefs: RefObject<HTMLInputElement | null>[][];
37
+ locale: Locale;
31
38
  reachableCells: boolean[][] | null;
32
39
  rows: CellModel[][];
33
40
  showCoordinates: ShowCoordinates;
34
41
  style?: CSSProperties;
42
+ translate: Translate;
35
43
  onBlur: FocusEventHandler;
36
44
  onChange: ChangeEventHandler<HTMLInputElement>;
37
45
  onFocus: (x: number, y: number) => void;
@@ -43,16 +51,17 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
43
51
  (
44
52
  {
45
53
  className,
46
- cellSize,
47
- coordinatesFontSize,
48
- coordinatesSize,
54
+ config,
49
55
  direction,
50
56
  cellFilters,
57
+ hoveredCharacter,
51
58
  inputRefs,
59
+ locale,
52
60
  reachableCells,
53
61
  rows,
54
62
  showCoordinates,
55
63
  style,
64
+ translate,
56
65
  onBlur,
57
66
  onChange,
58
67
  onFocus,
@@ -70,25 +79,13 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
70
79
  onKeyDown={onKeyDown}
71
80
  onPaste={onPaste}
72
81
  >
73
- {showCoordinates !== 'hidden' && (
74
- <>
75
- <div style={{ width: coordinatesSize, height: coordinatesSize }} />
82
+ <div />
76
83
 
77
- {rows[0].map((_column, index) => (
78
- <div
79
- className={styles.coordinate}
80
- key={index}
81
- style={{
82
- width: cellSize,
83
- height: coordinatesSize,
84
- fontSize: coordinatesFontSize,
85
- }}
86
- >
87
- {getCoordinate(index, showCoordinates === 'original' ? 'letter' : 'number')}
88
- </div>
89
- ))}
90
- </>
91
- )}
84
+ {rows[0].map((_column, index) => (
85
+ <div className={styles.coordinate} key={index}>
86
+ {getCoordinate(index, showCoordinates === 'original' ? 'letter' : 'number')}
87
+ </div>
88
+ ))}
92
89
 
93
90
  {/* The dynamic changes to the board presentation need to be outside of useBackgroundImage
94
91
  to prevent flickering on blob URL change (i.e. when flagging a field,
@@ -102,11 +99,11 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
102
99
  className={styles.iconContainer}
103
100
  key={[x, y].join('-')}
104
101
  style={{
105
- height: cellSize,
106
- width: cellSize,
107
- left: direction === 'ltr' ? coordinatesSize + BORDER_WIDTH + x * (cellSize + BORDER_WIDTH) : undefined,
108
- right: direction === 'rtl' ? coordinatesSize + BORDER_WIDTH + x * (cellSize + BORDER_WIDTH) : undefined,
109
- top: coordinatesSize + BORDER_WIDTH + y * (cellSize + BORDER_WIDTH),
102
+ height: 'var(--cell-size)',
103
+ width: 'var(--cell-size)',
104
+ left: direction === 'ltr' ? inlineOffset(x) : undefined,
105
+ right: direction === 'rtl' ? inlineOffset(x) : undefined,
106
+ top: inlineOffset(y),
110
107
  }}
111
108
  >
112
109
  <div className={styles.iconBackground} />
@@ -117,31 +114,25 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
117
114
 
118
115
  {rows.map((cells, y) => (
119
116
  <Fragment key={y}>
120
- {showCoordinates !== 'hidden' && (
121
- <div
122
- className={styles.coordinate}
123
- style={{
124
- width: coordinatesSize,
125
- height: cellSize,
126
- fontSize: coordinatesFontSize,
127
- }}
128
- >
129
- {getCoordinate(y, showCoordinates === 'original' ? 'number' : 'letter')}
130
- </div>
131
- )}
117
+ <div className={styles.coordinate}>
118
+ {getCoordinate(y, showCoordinates === 'original' ? 'number' : 'letter')}
119
+ </div>
132
120
 
133
121
  {cells.map((cell, x) => (
134
122
  <Cell
135
- className={styles.cell}
136
123
  cell={cell}
137
124
  cellBottom={y < rows.length - 1 ? rows[y + 1][x] : undefined}
138
125
  cellLeft={x > 0 ? rows[y][x - 1] : undefined}
139
126
  cellRight={x < rows[y].length - 1 ? rows[y][x + 1] : undefined}
140
127
  cellTop={y > 0 ? rows[y - 1][x] : undefined}
128
+ config={config}
141
129
  inputRef={inputRefs[y][x]}
130
+ isHoverMatch={isHoverMatch(cell, hoveredCharacter)}
142
131
  isReachable={reachableCells ? reachableCells[y][x] : true}
143
132
  key={x}
144
- size={cellSize}
133
+ locale={locale}
134
+ showCoordinates={showCoordinates}
135
+ translate={translate}
145
136
  onChange={onChange}
146
137
  onFocus={onFocus}
147
138
  />
@@ -153,3 +144,17 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
153
144
  );
154
145
 
155
146
  export const BoardPure = memo(BoardPureBase);
147
+
148
+ function isHoverMatch(cell: CellModel, hoveredCharacter: string | null): boolean {
149
+ if (cell.tile.character === EMPTY_CELL || hoveredCharacter === null) {
150
+ return false;
151
+ }
152
+
153
+ return hoveredCharacter === BLANK
154
+ ? cell.tile.isBlank
155
+ : !cell.tile.isBlank && cell.tile.character === hoveredCharacter;
156
+ }
157
+
158
+ function inlineOffset(index: number) {
159
+ return `calc(var(--coordinate-size) + var(--border--width) + ${index} * (var(--cell-size) + var(--border--width)))`;
160
+ }
@@ -6,28 +6,28 @@
6
6
  border-radius: var(--border--radius);
7
7
  transition: var(--transition);
8
8
  transition-property: opacity;
9
- }
10
9
 
11
- .action {
12
- padding: var(--spacing--m);
13
- box-shadow: none !important;
10
+ .action {
11
+ padding: var(--spacing--m);
12
+ box-shadow: none !important;
14
13
 
15
- &:active,
16
- &:hover {
17
- color: var(--color--foreground);
18
- }
14
+ &:active,
15
+ &:hover {
16
+ color: var(--color--foreground);
17
+ }
19
18
 
20
- & + & {
21
- border-inline-start: none;
22
- }
19
+ + .action {
20
+ border-inline-start: none;
21
+ }
23
22
 
24
- &:first-child {
25
- border-start-end-radius: 0;
26
- border-end-end-radius: 0;
27
- }
23
+ &:first-child {
24
+ border-start-end-radius: 0;
25
+ border-end-end-radius: 0;
26
+ }
28
27
 
29
- &:last-child {
30
- border-start-start-radius: 0;
31
- border-end-start-radius: 0;
28
+ &:last-child {
29
+ border-start-start-radius: 0;
30
+ border-end-start-radius: 0;
31
+ }
32
32
  }
33
33
  }
@@ -0,0 +1,7 @@
1
+ // Generated by scripts/generate-scss-types.ts - do not edit.
2
+ declare const styles: {
3
+ readonly action: string;
4
+ readonly actions: string;
5
+ };
6
+
7
+ export default styles;
@@ -3,9 +3,12 @@ import { type Cell } from '@scrabble-solver/types';
3
3
  import classNames from 'classnames';
4
4
  import { forwardRef, type HTMLProps, type MouseEventHandler } from 'react';
5
5
 
6
- import { useIsTouchDevice } from '@/hooks';
7
- import { Keyboard, Square, SquareFill } from '@/icons';
8
- import { findCell, isMac } from '@/lib';
6
+ import { useIsTouchDevice } from '@/hooks/useIsTouchDevice';
7
+ import Keyboard from '@/icons/Keyboard.svg';
8
+ import Square from '@/icons/Square.svg';
9
+ import SquareFill from '@/icons/SquareFill.svg';
10
+ import { findCell } from '@/lib/findCell';
11
+ import { isMac } from '@/lib/isMac';
9
12
  import { selectCellFilter, selectInputMode, selectResultCandidateCells, useTranslate, useTypedSelector } from '@/state';
10
13
  import { type Direction } from '@/types';
11
14
 
@@ -1,6 +1,8 @@
1
1
  import { type FunctionComponent, type SVGAttributes } from 'react';
2
2
 
3
- import { Ban, Eraser, FlagFill } from '@/icons';
3
+ import Ban from '@/icons/Ban.svg';
4
+ import Eraser from '@/icons/Eraser.svg';
5
+ import FlagFill from '@/icons/FlagFill.svg';
4
6
  import { type CellFilter, type TranslationKey } from '@/types';
5
7
 
6
8
  export const getNextCellFilter = (
@@ -0,0 +1,13 @@
1
+ // Generated by scripts/generate-scss-types.ts - do not edit.
2
+ declare const styles: {
3
+ readonly first3: string;
4
+ readonly last3: string;
5
+ readonly sharpBottomLeft: string;
6
+ readonly sharpBottomRight: string;
7
+ readonly sharpTopLeft: string;
8
+ readonly sharpTopRight: string;
9
+ readonly tile: string;
10
+ readonly unreachable: string;
11
+ };
12
+
13
+ export default styles;
@@ -1,5 +1,5 @@
1
- import { BLANK, EMPTY_CELL } from '@scrabble-solver/constants';
2
- import { type Cell as CellModel } from '@scrabble-solver/types';
1
+ import { EMPTY_CELL } from '@scrabble-solver/constants';
2
+ import { type Cell as CellModel, type Config, type Locale, type ShowCoordinates } from '@scrabble-solver/types';
3
3
  import classNames from 'classnames';
4
4
  import {
5
5
  type ChangeEventHandler,
@@ -12,17 +12,9 @@ import {
12
12
  useCallback,
13
13
  } from 'react';
14
14
 
15
- import { getCoordinate } from '@/lib';
16
- import {
17
- selectCellIsValid,
18
- selectConfig,
19
- selectHoveredCharacter,
20
- selectInputMode,
21
- selectShowCoordinates,
22
- selectTilePoints,
23
- useTranslate,
24
- useTypedSelector,
25
- } from '@/state';
15
+ import { getCoordinate } from '@/lib/getCoordinate';
16
+ import { selectInputMode, useTypedStore } from '@/state';
17
+ import type { Translate } from '@/types';
26
18
 
27
19
  import { Tile } from '../../../Tile';
28
20
 
@@ -35,13 +27,20 @@ interface Props {
35
27
  cellRight?: CellModel;
36
28
  cellTop?: CellModel;
37
29
  className?: string;
30
+ config: Config;
38
31
  inputRef: RefObject<HTMLInputElement | null>;
32
+ isHoverMatch: boolean;
39
33
  isReachable?: boolean;
40
- size: number;
34
+ locale: Locale;
35
+ showCoordinates: ShowCoordinates;
36
+ translate: Translate;
41
37
  onChange: ChangeEventHandler<HTMLInputElement>;
42
38
  onFocus: (x: number, y: number) => void;
43
39
  }
44
40
 
41
+ /**
42
+ * Must not subscribe to the store - it can read the story only in event handlers.
43
+ */
45
44
  const CellBase: FunctionComponent<Props> = ({
46
45
  cell,
47
46
  cellBottom,
@@ -49,58 +48,54 @@ const CellBase: FunctionComponent<Props> = ({
49
48
  cellRight,
50
49
  cellTop,
51
50
  className,
51
+ config,
52
52
  inputRef,
53
+ isHoverMatch,
53
54
  isReachable = true,
54
- size,
55
+ locale,
56
+ showCoordinates,
57
+ translate,
55
58
  onChange,
56
59
  onFocus,
57
60
  }) => {
58
61
  const { tile, x, y } = cell;
59
- const translate = useTranslate();
60
- const config = useTypedSelector(selectConfig);
61
- const inputMode = useTypedSelector(selectInputMode);
62
- const showCoordinates = useTypedSelector(selectShowCoordinates);
63
- const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
64
- const isValid = useTypedSelector((state) => selectCellIsValid(state, cell));
65
- const hoveredCharacter = useTypedSelector(selectHoveredCharacter);
62
+ const store = useTypedStore();
66
63
  const isEmpty = tile.character === EMPTY_CELL;
67
- const isHoverMatch =
68
- !isEmpty &&
69
- hoveredCharacter !== null &&
70
- (hoveredCharacter === BLANK ? tile.isBlank : !tile.isBlank && tile.character === hoveredCharacter);
64
+ const points = config.getTilePoints(tile);
65
+ const isValid = !cell.hasTile() || config.tiles.some(({ character }) => character === tile.character);
71
66
 
72
67
  const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
73
68
  (event) => {
74
- if (inputMode === 'touchscreen') {
69
+ if (selectInputMode(store.getState()) === 'touchscreen') {
75
70
  event.preventDefault();
76
71
  event.target.blur();
77
72
  }
78
73
 
79
74
  onFocus(x, y);
80
75
  },
81
- [inputMode, onFocus, x, y],
76
+ [store, onFocus, x, y],
82
77
  );
83
78
 
84
79
  const handleMouseDown: MouseEventHandler<HTMLInputElement> = useCallback(
85
80
  (event) => {
86
- if (inputMode === 'touchscreen') {
81
+ if (selectInputMode(store.getState()) === 'touchscreen') {
87
82
  event.preventDefault();
88
83
  }
89
84
 
90
85
  onFocus(x, y);
91
86
  },
92
- [inputMode, onFocus, x, y],
87
+ [store, onFocus, x, y],
93
88
  );
94
89
 
95
90
  const handleTouchStart: TouchEventHandler<HTMLInputElement> = useCallback(
96
91
  (event) => {
97
- if (inputMode === 'touchscreen') {
92
+ if (selectInputMode(store.getState()) === 'touchscreen') {
98
93
  event.preventDefault();
99
94
  }
100
95
 
101
96
  onFocus(x, y);
102
97
  },
103
- [inputMode, onFocus, x, y],
98
+ [store, onFocus, x, y],
104
99
  );
105
100
 
106
101
  return (
@@ -125,9 +120,9 @@ const CellBase: FunctionComponent<Props> = ({
125
120
  inputRef={inputRef}
126
121
  isBlank={tile.isBlank}
127
122
  isValid={isValid}
123
+ locale={locale}
128
124
  points={points}
129
125
  raised={!isEmpty}
130
- size={size}
131
126
  tabIndex={cell.x === 0 && cell.y === 0 ? undefined : -1}
132
127
  onChange={onChange}
133
128
  onFocus={handleFocus}
@@ -0,0 +1,10 @@
1
+ // Generated by scripts/generate-scss-types.ts - do not edit.
2
+ declare const styles: {
3
+ readonly input: string;
4
+ readonly inputPrompt: string;
5
+ readonly insert: string;
6
+ readonly insertIcon: string;
7
+ readonly toggleDirection: string;
8
+ };
9
+
10
+ export default styles;
@@ -8,7 +8,7 @@ import {
8
8
  useState,
9
9
  } from 'react';
10
10
 
11
- import { Check } from '@/icons';
11
+ import Check from '@/icons/Check.svg';
12
12
  import { useTranslate } from '@/state';
13
13
  import { type Direction } from '@/types';
14
14
 
@@ -0,0 +1,8 @@
1
+ // Generated by scripts/generate-scss-types.ts - do not edit.
2
+ declare const styles: {
3
+ readonly button: string;
4
+ readonly icon: string;
5
+ readonly right: string;
6
+ };
7
+
8
+ export default styles;
@@ -1,8 +1,8 @@
1
1
  import classNames from 'classnames';
2
2
  import { type ButtonHTMLAttributes, type FunctionComponent } from 'react';
3
3
 
4
- import { useIsTouchDevice } from '@/hooks';
5
- import { ArrowDown } from '@/icons';
4
+ import { useIsTouchDevice } from '@/hooks/useIsTouchDevice';
5
+ import ArrowDown from '@/icons/ArrowDown.svg';
6
6
  import { useTranslate } from '@/state';
7
7
  import { type Direction } from '@/types';
8
8
 
@@ -0,0 +1,175 @@
1
+ import { BONUS_WORD } from '@scrabble-solver/constants';
2
+ import { type Bonus, type Config, type TextDirection } from '@scrabble-solver/types';
3
+
4
+ import { BORDER_COLOR_LIGHT, BORDER_WIDTH, COLOR_BACKGROUND, COLOR_BONUS_START } from '@/parameters';
5
+ import { type Point } from '@/types';
6
+
7
+ import { getBonusColor } from './lib';
8
+
9
+ interface BoardBackgroundOptions {
10
+ config: Config;
11
+ direction: TextDirection;
12
+ }
13
+
14
+ interface Frame {
15
+ width: number;
16
+ height: number;
17
+ direction: TextDirection;
18
+ }
19
+
20
+ interface BonusesOptions {
21
+ config: Config;
22
+ getX: (point: Point) => number;
23
+ getY: (point: Point) => number;
24
+ }
25
+
26
+ const CELL_SIZE = 80;
27
+ const COORDINATES_SIZE = CELL_SIZE / 2;
28
+ const BORDER_RADIUS = CELL_SIZE * 0.1;
29
+ const BONUS_SIZE = CELL_SIZE * 0.8;
30
+ const BONUS_OFFSET = CELL_SIZE * 0.1;
31
+ const ICON_SIZE = CELL_SIZE * 0.4;
32
+ const ICON_OFFSET = (CELL_SIZE - ICON_SIZE) / 2;
33
+ const FONT_SIZE = CELL_SIZE * 0.6 * 0.6;
34
+ const FONT_OFFSET = CELL_SIZE / 2;
35
+ const GRID_LINE_SIZE = 1;
36
+
37
+ const BONUS_SYMBOL_ID = 'b';
38
+ const BONUS_X2_SYMBOL_ID = 'b2';
39
+ const BONUS_X3_SYMBOL_ID = 'b3';
40
+ const BONUS_X4_SYMBOL_ID = 'b4';
41
+
42
+ // https://icons.getbootstrap.com/icons/star-fill/
43
+ const STAR_PATH =
44
+ 'M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z';
45
+
46
+ const cache = new WeakMap<Config, Map<TextDirection, string>>();
47
+
48
+ export function getBoardBackground({ config, direction }: BoardBackgroundOptions): string {
49
+ const cached = cache.get(config)?.get(direction);
50
+
51
+ if (cached) {
52
+ return cached;
53
+ }
54
+
55
+ const dataUrl = `data:image/svg+xml,${encodeURIComponent(buildSvg({ config, direction }))}`;
56
+ const byDirection = cache.get(config) ?? new Map<TextDirection, string>();
57
+ byDirection.set(direction, dataUrl);
58
+ cache.set(config, byDirection);
59
+
60
+ return dataUrl;
61
+ }
62
+
63
+ function buildSvg({ config, direction }: BoardBackgroundOptions): string {
64
+ const width = COORDINATES_SIZE + (CELL_SIZE + BORDER_WIDTH) * config.boardWidth - BORDER_WIDTH;
65
+ const height = COORDINATES_SIZE + (CELL_SIZE + BORDER_WIDTH) * config.boardHeight - BORDER_WIDTH;
66
+ const getX = (point: Point) => (direction === 'ltr' ? COORDINATES_SIZE : 0) + point.x * (CELL_SIZE + BORDER_WIDTH);
67
+ const getY = (point: Point) => COORDINATES_SIZE + point.y * (CELL_SIZE + BORDER_WIDTH);
68
+ const center = { x: Math.floor(config.boardWidth / 2), y: Math.floor(config.boardHeight / 2) };
69
+
70
+ const frame: Frame = { direction, height, width };
71
+
72
+ return [
73
+ `<svg height="${height}" viewBox="0 0 ${width} ${height}" width="${width}" xmlns="http://www.w3.org/2000/svg">`,
74
+ buildDefs(),
75
+ buildBackground(frame),
76
+ buildGridLines(config, frame),
77
+ buildBonuses({ config, getX, getY }),
78
+ buildStartCell(getX(center), getY(center)),
79
+ '</svg>',
80
+ ].join('');
81
+ }
82
+
83
+ function buildDefs(): string {
84
+ const bonusRect = `<rect height="${BONUS_SIZE}" rx="${BORDER_RADIUS}" width="${BONUS_SIZE}" x="${BONUS_OFFSET}" y="${BONUS_OFFSET}"/>`;
85
+
86
+ return [
87
+ '<defs>',
88
+ `<symbol id="${BONUS_SYMBOL_ID}">${bonusRect}</symbol>`,
89
+ `<symbol id="${BONUS_X2_SYMBOL_ID}">${bonusRect}${multiplierText(2)}</symbol>`,
90
+ `<symbol id="${BONUS_X3_SYMBOL_ID}">${bonusRect}${multiplierText(3)}</symbol>`,
91
+ `<symbol id="${BONUS_X4_SYMBOL_ID}">${bonusRect}${multiplierText(4)}</symbol>`,
92
+ '</defs>',
93
+ ].join('');
94
+ }
95
+
96
+ function multiplierText(multiplier: number): string {
97
+ return `<text dominant-baseline="central" fill="white" font-family="system-ui, sans-serif" font-size="${FONT_SIZE}" font-weight="bold" text-anchor="middle" x="${FONT_OFFSET}" y="${FONT_OFFSET}">×${multiplier}</text>`;
98
+ }
99
+
100
+ function buildBackground({ direction, height, width }: Frame): string {
101
+ const cellsX = direction === 'ltr' ? COORDINATES_SIZE : 0;
102
+ const coordinatesColumnX = direction === 'ltr' ? 0 : width - COORDINATES_SIZE + BORDER_WIDTH;
103
+
104
+ return [
105
+ `<rect fill="${COLOR_BACKGROUND}" height="${height}" rx="${BORDER_RADIUS}" width="${width}" x="0" y="0"/>`,
106
+ `<rect fill="white" height="${height - COORDINATES_SIZE}" width="${width - COORDINATES_SIZE}" x="${cellsX}" y="${COORDINATES_SIZE}"/>`,
107
+ `<rect fill="${COLOR_BACKGROUND}" height="${COORDINATES_SIZE}" rx="${BORDER_RADIUS}" width="${width}" x="0" y="0"/>`,
108
+ `<rect fill="${COLOR_BACKGROUND}" height="${height}" rx="${BORDER_RADIUS}" width="${COORDINATES_SIZE}" x="${coordinatesColumnX}" y="0"/>`,
109
+ ].join('');
110
+ }
111
+
112
+ function buildGridLines(config: Config, { direction, height, width }: Frame): string {
113
+ const lines: string[] = [];
114
+
115
+ lines.push(horizontalLine(COORDINATES_SIZE - BORDER_WIDTH / 2, width));
116
+ lines.push(
117
+ verticalLine(
118
+ direction === 'ltr' ? COORDINATES_SIZE - BORDER_WIDTH / 2 : width - COORDINATES_SIZE + BORDER_WIDTH / 2,
119
+ height,
120
+ ),
121
+ );
122
+
123
+ for (let index = 1; index < config.boardHeight; ++index) {
124
+ lines.push(horizontalLine(COORDINATES_SIZE + index * (CELL_SIZE + BORDER_WIDTH) - BORDER_WIDTH / 2, width));
125
+ }
126
+
127
+ const cellsX = direction === 'ltr' ? COORDINATES_SIZE : 0;
128
+
129
+ for (let index = 1; index < config.boardWidth; ++index) {
130
+ lines.push(verticalLine(cellsX + index * (CELL_SIZE + BORDER_WIDTH) - BORDER_WIDTH / 2, height));
131
+ }
132
+
133
+ return lines.join('');
134
+ }
135
+
136
+ function horizontalLine(y: number, width: number): string {
137
+ return `<line stroke="${BORDER_COLOR_LIGHT}" stroke-width="${GRID_LINE_SIZE}" vector-effect="non-scaling-stroke" x1="0" x2="${width}" y1="${y}" y2="${y}"/>`;
138
+ }
139
+
140
+ function verticalLine(x: number, height: number): string {
141
+ return `<line stroke="${BORDER_COLOR_LIGHT}" stroke-width="${GRID_LINE_SIZE}" vector-effect="non-scaling-stroke" x1="${x}" x2="${x}" y1="0" y2="${height}"/>`;
142
+ }
143
+
144
+ function buildBonuses({ config, getX, getY }: BonusesOptions): string {
145
+ return config.bonuses
146
+ .map(
147
+ (bonus) =>
148
+ `<use fill="${getBonusColor(bonus)}" href="#${getSymbolId(bonus)}" x="${getX(bonus)}" y="${getY(bonus)}"/>`,
149
+ )
150
+ .join('');
151
+ }
152
+
153
+ function getSymbolId(bonus: Bonus): string {
154
+ if (bonus.type !== BONUS_WORD) {
155
+ return BONUS_SYMBOL_ID;
156
+ }
157
+
158
+ switch (bonus.multiplier) {
159
+ case 2:
160
+ return BONUS_X2_SYMBOL_ID;
161
+ case 3:
162
+ return BONUS_X3_SYMBOL_ID;
163
+ case 4:
164
+ return BONUS_X4_SYMBOL_ID;
165
+ default:
166
+ return BONUS_SYMBOL_ID;
167
+ }
168
+ }
169
+
170
+ function buildStartCell(x: number, y: number): string {
171
+ return [
172
+ `<rect fill="${COLOR_BONUS_START}" height="${BONUS_SIZE}" rx="${BORDER_RADIUS}" width="${BONUS_SIZE}" x="${x + BONUS_OFFSET}" y="${y + BONUS_OFFSET}"/>`,
173
+ `<svg height="${ICON_SIZE}" viewBox="0 0 16 16" width="${ICON_SIZE}" x="${x + ICON_OFFSET}" y="${y + ICON_OFFSET}"><path d="${STAR_PATH}" fill="white"/></svg>`,
174
+ ].join('');
175
+ }
@@ -1,4 +1,3 @@
1
- export { useBackgroundImage } from './useBackgroundImage';
2
1
  export { useBoardStyle } from './useBoardStyle';
3
2
  export { useFloatingActions } from './useFloatingActions';
4
3
  export { useFloatingFocus } from './useFloatingFocus';
@@ -1,33 +1,23 @@
1
1
  import { type CSSProperties, useMemo } from 'react';
2
2
 
3
- import { useAppLayout } from '@/app-layout';
4
- import { getTileSizes } from '@/lib';
5
- import { BORDER_WIDTH } from '@/parameters';
6
- import { selectConfig, selectShowCoordinates, useTypedSelector } from '@/state';
3
+ import { LOCALE_FEATURES } from '@/i18n/constants';
4
+ import { selectConfig, selectLocale, useTypedSelector } from '@/state';
7
5
 
8
- import { useBackgroundImage } from './useBackgroundImage';
6
+ import { getBoardBackground } from '../getBoardBackground';
9
7
 
8
+ /**
9
+ * Everything here is derived from Redux state only, so the style on the server matches the client
10
+ */
10
11
  export const useBoardStyle = () => {
11
12
  const config = useTypedSelector(selectConfig);
12
- const { cellSize } = useAppLayout();
13
- const { tileFontSize } = getTileSizes(cellSize);
14
- const showCoordinates = useTypedSelector(selectShowCoordinates);
15
- const backgroundImage = useBackgroundImage();
16
- const coordinatesSize = 0.5 * cellSize - BORDER_WIDTH;
13
+ const locale = useTypedSelector(selectLocale);
14
+ const { direction } = LOCALE_FEATURES[locale];
15
+ const backgroundImage = getBoardBackground({ config, direction });
17
16
  const boardStyle = useMemo<CSSProperties>(
18
17
  () => ({
19
18
  backgroundImage: `url(${backgroundImage})`,
20
- fontSize: tileFontSize,
21
- gridTemplateColumns:
22
- showCoordinates === 'hidden'
23
- ? `repeat(${config.boardWidth}, 1fr)`
24
- : `${coordinatesSize}px repeat(${config.boardWidth}, 1fr)`,
25
- gridTemplateRows:
26
- showCoordinates === 'hidden'
27
- ? `repeat(${config.boardHeight}, 1fr)`
28
- : `${coordinatesSize}px repeat(${config.boardHeight}, 1fr)`,
29
19
  }),
30
- [backgroundImage, config.boardHeight, config.boardWidth, coordinatesSize, showCoordinates, tileFontSize],
20
+ [backgroundImage],
31
21
  );
32
22
 
33
23
  return boardStyle;