@scrabble-solver/scrabble-solver 2.10.7 → 2.10.8

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 (95) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +7 -13
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +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/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-server.js.nft.json +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/176.js +4273 -214
  16. package/.next/server/middleware-build-manifest.js +1 -1
  17. package/.next/server/pages/404.html +2 -2
  18. package/.next/server/pages/404.js.nft.json +1 -1
  19. package/.next/server/pages/500.html +2 -2
  20. package/.next/server/pages/_app.js +296 -13
  21. package/.next/server/pages/_app.js.nft.json +1 -1
  22. package/.next/server/pages/_document.js.nft.json +1 -1
  23. package/.next/server/pages/api/solve.js +17 -0
  24. package/.next/server/pages/index.html +1 -9
  25. package/.next/server/pages/index.js +23 -16
  26. package/.next/server/pages/index.js.nft.json +1 -1
  27. package/.next/server/pages/index.json +1 -1
  28. package/.next/server/pages-manifest.json +1 -1
  29. package/.next/static/Cs23uxWG6AxS72F2yrjHu/_buildManifest.js +1 -0
  30. package/.next/static/chunks/pages/{404-67383848027ec49b.js → 404-8cab6d62fe4ead73.js} +1 -1
  31. package/.next/static/chunks/pages/_app-dcbbb823dc93a031.js +28 -0
  32. package/.next/static/chunks/pages/index-df1ff01aa82d2d4d.js +1 -0
  33. package/.next/static/css/bf2e969b88c4e3dd.css +2 -0
  34. package/.next/static/css/d1cc6b79b211b7b8.css +1 -0
  35. package/.next/trace +55 -55
  36. package/package.json +10 -9
  37. package/src/components/Badge/Badge.module.scss +1 -1
  38. package/src/components/Board/components/Cell/Cell.module.scss +32 -64
  39. package/src/components/Board/components/Cell/CellPure.tsx +15 -22
  40. package/src/components/Button/Button.module.scss +2 -2
  41. package/src/components/Checkbox/Checkbox.tsx +1 -4
  42. package/src/components/Dictionary/Dictionary.tsx +28 -30
  43. package/src/components/DictionaryInput/DictionaryInput.tsx +3 -0
  44. package/src/components/Key/Key.module.scss +1 -1
  45. package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +1 -1
  46. package/src/components/Modal/Modal.module.scss +4 -2
  47. package/src/components/Rack/Rack.module.scss +4 -0
  48. package/src/components/Radio/Radio.tsx +1 -4
  49. package/src/components/Results/Results.module.scss +2 -2
  50. package/src/components/SeoMessage/SeoMessage.tsx +19 -0
  51. package/src/components/SeoMessage/index.ts +1 -0
  52. package/src/components/Solver/components/EmptyState/EmptyState.module.scss +1 -1
  53. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +9 -2
  54. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -1
  55. package/src/components/Tile/Tile.module.scss +49 -11
  56. package/src/components/Tile/Tile.tsx +23 -8
  57. package/src/components/Tile/TilePure.tsx +27 -20
  58. package/src/components/Tooltip/Tooltip.module.scss +7 -7
  59. package/src/components/index.ts +1 -0
  60. package/src/i18n/de.json +1 -0
  61. package/src/i18n/en.json +1 -0
  62. package/src/i18n/es.json +1 -0
  63. package/src/i18n/fa.json +1 -0
  64. package/src/i18n/fr.json +1 -0
  65. package/src/i18n/pl.json +1 -0
  66. package/src/icons/Flag.svg +2 -2
  67. package/src/icons/FlagFill.svg +4 -0
  68. package/src/icons/Square.svg +4 -0
  69. package/src/icons/SquareFill.svg +4 -0
  70. package/src/icons/index.ts +3 -0
  71. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -1
  72. package/src/modals/ResultsModal/ResultsModal.module.scss +1 -1
  73. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -2
  74. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -2
  75. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +14 -24
  76. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +1 -2
  77. package/src/pages/_app.tsx +9 -5
  78. package/src/pages/index.module.scss +1 -2
  79. package/src/pages/index.tsx +10 -8
  80. package/src/parameters/index.ts +10 -0
  81. package/src/state/slices/boardSlice.ts +5 -5
  82. package/src/styles/mixins.scss +4 -2
  83. package/src/styles/variables.scss +39 -32
  84. package/src/types/index.ts +1 -0
  85. package/.next/server/chunks/579.js +0 -3925
  86. package/.next/static/6RggBFm8kHrh-k1-CG3um/_buildManifest.js +0 -1
  87. package/.next/static/chunks/490-d29992f1c264d70e.js +0 -5
  88. package/.next/static/chunks/509-6ad4482d4351452c.js +0 -1
  89. package/.next/static/chunks/pages/_app-c58cfa832b76cc87.js +0 -24
  90. package/.next/static/chunks/pages/index-146039f501e49c08.js +0 -1
  91. package/.next/static/css/4482c4a0064d3807.css +0 -1
  92. package/.next/static/css/78e42ad01f580f64.css +0 -1
  93. package/.next/static/css/9d1013ec684361b9.css +0 -1
  94. package/src/components/Board/components/Cell/Button.tsx +0 -32
  95. /package/.next/static/{6RggBFm8kHrh-k1-CG3um → Cs23uxWG6AxS72F2yrjHu}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.10.7",
3
+ "version": "2.10.8",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -31,16 +31,17 @@
31
31
  "@kamilmielnik/trie": "^2.0.1",
32
32
  "@popperjs/core": "^2.11.6",
33
33
  "@reduxjs/toolkit": "^1.9.3",
34
- "@scrabble-solver/configs": "^2.10.7",
35
- "@scrabble-solver/constants": "^2.10.7",
36
- "@scrabble-solver/dictionaries": "^2.10.7",
37
- "@scrabble-solver/logger": "^2.10.7",
38
- "@scrabble-solver/solver": "^2.10.7",
39
- "@scrabble-solver/types": "^2.10.7",
40
- "@scrabble-solver/word-definitions": "^2.10.7",
34
+ "@scrabble-solver/configs": "^2.10.8",
35
+ "@scrabble-solver/constants": "^2.10.8",
36
+ "@scrabble-solver/dictionaries": "^2.10.8",
37
+ "@scrabble-solver/logger": "^2.10.8",
38
+ "@scrabble-solver/solver": "^2.10.8",
39
+ "@scrabble-solver/types": "^2.10.8",
40
+ "@scrabble-solver/word-definitions": "^2.10.8",
41
41
  "classnames": "^2.3.2",
42
42
  "include-media": "^2.0.0",
43
43
  "include-media-query-builder": "^1.1.0",
44
+ "merge-refs": "^1.1.2",
44
45
  "next": "^13.1.6",
45
46
  "normalize.css": "^8.0.1",
46
47
  "react": "^18.2.0",
@@ -76,5 +77,5 @@
76
77
  "sass": "^1.58.3",
77
78
  "workbox-webpack-plugin": "^6.5.4"
78
79
  },
79
- "gitHead": "4d40b64882d1b9866d70ebe6a239d7d018b0cd18"
80
+ "gitHead": "faa8a69c239fd14a2b07fe18744580e8713e93ea"
80
81
  }
@@ -5,7 +5,7 @@
5
5
  padding: var(--spacing--xs) var(--spacing--m);
6
6
  border: var(--border);
7
7
  border-radius: var(--border--radius);
8
- background-color: var(--color--background--overlay);
8
+ background-color: var(--color--background--element);
9
9
  line-height: var(--line-height);
10
10
  font-size: var(--font--size--m);
11
11
  font-weight: bold;
@@ -1,13 +1,11 @@
1
1
  @import 'styles/mixins';
2
2
 
3
- $icon-size: 16px;
4
-
5
3
  .cell {
6
4
  @include focus-effect;
7
5
 
8
6
  position: relative;
9
7
  display: table-cell;
10
- background-color: white;
8
+ background-color: var(--color--white);
11
9
  border-bottom: var(--border--width) dotted var(--border--color--light);
12
10
  transition: var(--transition);
13
11
  background-clip: padding-box;
@@ -36,57 +34,57 @@ $icon-size: 16px;
36
34
 
37
35
  &.bonusStart {
38
36
  &::before {
39
- background-color: var(--color--violet--light);
37
+ background-color: var(--color--bonus--start);
40
38
  }
41
39
  }
42
40
 
43
41
  &.bonusCharacter1 {
44
42
  &::before {
45
- background-color: var(--color--yellow--light);
43
+ background-color: var(--color--bonus--character--1);
46
44
  }
47
45
  }
48
46
 
49
47
  &.bonusCharacter2 {
50
48
  &::before {
51
- background-color: var(--color--green--light);
49
+ background-color: var(--color--bonus--character--2);
52
50
  }
53
51
  }
54
52
 
55
53
  &.bonusCharacter3 {
56
54
  &::before {
57
- background-color: var(--color--blue--light);
55
+ background-color: var(--color--bonus--character--3);
58
56
  }
59
57
  }
60
58
 
61
59
  &.bonusCharacter5 {
62
60
  &::before {
63
- background-color: var(--color--red--light);
61
+ background-color: var(--color--bonus--character--5);
64
62
  }
65
63
  }
66
64
 
67
65
  &.bonusCharacterMultiplier2 {
68
66
  &::before {
69
- background-color: var(--color--light-blue--light);
67
+ background-color: var(--color--bonus--character-multiplier--2);
70
68
  }
71
69
  }
72
70
 
73
71
  &.bonusCharacterMultiplier3 {
74
72
  &::before {
75
- background-color: var(--color--dark-blue--light);
73
+ background-color: var(--color--bonus--character-multiplier--3);
76
74
  }
77
75
  }
78
76
 
79
77
  &.bonusWord2 {
80
78
  &::before {
81
79
  content: 'x2';
82
- background-color: var(--color--orange);
80
+ background-color: var(--color--bonus--word-multiplier--2);
83
81
  }
84
82
  }
85
83
 
86
84
  &.bonusWord3 {
87
85
  &::before {
88
86
  content: 'x3';
89
- background-color: var(--color--pink);
87
+ background-color: var(--color--bonus--word-multiplier--3);
90
88
  }
91
89
  }
92
90
 
@@ -112,7 +110,7 @@ $icon-size: 16px;
112
110
  pointer-events: none;
113
111
  font-size: 60%;
114
112
  font-weight: bold;
115
- color: white;
113
+ color: var(--color--white);
116
114
  content: ' ';
117
115
 
118
116
  [lang='fa-IR'] & {
@@ -121,7 +119,7 @@ $icon-size: 16px;
121
119
  }
122
120
 
123
121
  &:focus-within {
124
- z-index: 1;
122
+ z-index: 2;
125
123
 
126
124
  .actions {
127
125
  display: flex;
@@ -135,9 +133,15 @@ $icon-size: 16px;
135
133
  }
136
134
 
137
135
  .actions {
136
+ --offset: var(--spacing--m);
137
+
138
+ @include media('<xs') {
139
+ --offset: var(--spacing--s);
140
+ }
141
+
138
142
  display: none;
139
143
  position: absolute;
140
- top: -$icon-size;
144
+ bottom: calc(100% - var(--offset));
141
145
  z-index: 2;
142
146
  transition: var(--transition);
143
147
  pointer-events: none;
@@ -145,30 +149,17 @@ $icon-size: 16px;
145
149
  border-radius: var(--border--radius);
146
150
 
147
151
  [dir='ltr'] & {
148
- left: calc(100% + var(--spacing--s) - #{$icon-size});
152
+ left: calc(100% - var(--offset));
149
153
  }
150
154
 
151
155
  [dir='rtl'] & {
152
- right: calc(100% + var(--spacing--s) - #{$icon-size});
156
+ right: calc(100% - var(--offset));
153
157
  }
154
158
  }
155
159
 
156
160
  .action {
157
- @include button-reset;
158
-
159
- display: flex;
160
- justify-content: center;
161
- padding: var(--spacing--s);
162
- user-select: none;
163
- pointer-events: none;
164
- box-sizing: content-box;
165
- background-color: white;
166
- border: var(--border);
167
- font-size: var(--font--size--m);
168
- line-height: $icon-size;
169
- color: var(--color--foreground--secondary);
170
- transition: var(--transition);
171
- cursor: pointer;
161
+ padding: var(--spacing--m);
162
+ box-shadow: none !important;
172
163
 
173
164
  & + & {
174
165
  [dir='ltr'] & {
@@ -182,35 +173,28 @@ $icon-size: 16px;
182
173
 
183
174
  [dir='ltr'] & {
184
175
  &:first-child {
185
- border-top-left-radius: var(--border--radius);
186
- border-bottom-left-radius: var(--border--radius);
176
+ border-top-right-radius: 0;
177
+ border-bottom-right-radius: 0;
187
178
  }
188
179
 
189
180
  &:last-child {
190
- border-top-right-radius: var(--border--radius);
191
- border-bottom-right-radius: var(--border--radius);
181
+ border-top-left-radius: 0;
182
+ border-bottom-left-radius: 0;
192
183
  }
193
184
  }
194
185
 
195
186
  [dir='rtl'] & {
196
187
  &:first-child {
197
- border-top-right-radius: var(--border--radius);
198
- border-bottom-right-radius: var(--border--radius);
188
+ border-top-left-radius: 0;
189
+ border-bottom-left-radius: 0;
199
190
  }
200
191
 
201
192
  &:last-child {
202
- border-top-left-radius: var(--border--radius);
203
- border-bottom-left-radius: var(--border--radius);
193
+ border-top-right-radius: 0;
194
+ border-bottom-right-radius: 0;
204
195
  }
205
196
  }
206
197
 
207
- &,
208
- .filterCell,
209
- .toggleDirection {
210
- width: $icon-size;
211
- height: $icon-size;
212
- }
213
-
214
198
  &:active,
215
199
  &:hover {
216
200
  color: var(--color--foreground);
@@ -229,22 +213,6 @@ $icon-size: 16px;
229
213
  }
230
214
  }
231
215
 
232
- .blank {
233
- user-select: none;
234
-
235
- &.active {
236
- font-weight: bold;
237
- }
238
- }
239
-
240
- .filterCell {
241
- color: var(--color--inactive);
242
-
243
- &.filtered {
244
- color: var(--color--foreground--secondary);
245
- }
246
- }
247
-
248
216
  .iconContainer {
249
217
  position: absolute;
250
218
  top: 0;
@@ -262,7 +230,7 @@ $icon-size: 16px;
262
230
 
263
231
  .flag,
264
232
  .star {
265
- color: white;
233
+ color: var(--color--white);
266
234
  }
267
235
 
268
236
  .star {
@@ -10,12 +10,12 @@ import {
10
10
  RefObject,
11
11
  } from 'react';
12
12
 
13
- import { ArrowDown, Flag, Star } from 'icons';
13
+ import { ArrowDown, Flag, FlagFill, Square, SquareFill, Star } from 'icons';
14
14
  import { Translate } from 'types';
15
15
 
16
+ import Button from '../../../Button';
16
17
  import Tile from '../../../Tile';
17
18
 
18
- import Button from './Button';
19
19
  import styles from './Cell.module.scss';
20
20
  import { getBonusClassname } from './lib';
21
21
 
@@ -84,7 +84,7 @@ const CellPure: FunctionComponent<Props> = ({
84
84
 
85
85
  {isFiltered && (
86
86
  <div className={classNames(styles.iconContainer, styles.flagContainer)}>
87
- <Flag className={styles.flag} />
87
+ <FlagFill className={styles.flag} />
88
88
  </div>
89
89
  )}
90
90
 
@@ -108,40 +108,33 @@ const CellPure: FunctionComponent<Props> = ({
108
108
  <div className={styles.actions}>
109
109
  <Button
110
110
  aria-label={translate('cell.toggle-direction')}
111
+ className={styles.action}
112
+ Icon={ArrowDown}
113
+ iconClassName={classNames(styles.toggleDirection, {
114
+ [styles.right]: direction === 'horizontal',
115
+ })}
111
116
  tooltip={translate('cell.toggle-direction')}
112
117
  onClick={onDirectionToggleClick}
113
- >
114
- <ArrowDown
115
- className={classNames(styles.toggleDirection, {
116
- [styles.right]: direction === 'horizontal',
117
- })}
118
- />
119
- </Button>
118
+ />
120
119
 
121
120
  {isEmpty && (
122
121
  <Button
123
122
  aria-label={translate('cell.filter-cell')}
124
- className={classNames(styles.filterCell, {
125
- [styles.filtered]: isFiltered,
126
- })}
123
+ className={classNames(styles.action)}
124
+ Icon={isFiltered ? Flag : FlagFill}
127
125
  tooltip={translate('cell.filter-cell')}
128
126
  onClick={onToggleFilterCellClick}
129
- >
130
- <Flag />
131
- </Button>
127
+ />
132
128
  )}
133
129
 
134
130
  {!isEmpty && (
135
131
  <Button
136
132
  aria-label={tile.isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
137
- className={classNames(styles.blank, {
138
- [styles.active]: tile.isBlank,
139
- })}
133
+ className={styles.action}
134
+ Icon={tile.isBlank ? SquareFill : Square}
140
135
  tooltip={tile.isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
141
136
  onClick={onToggleBlankClick}
142
- >
143
- B
144
- </Button>
137
+ />
145
138
  )}
146
139
  </div>
147
140
  )}
@@ -21,11 +21,11 @@
21
21
 
22
22
  &.primary {
23
23
  background-color: var(--color--primary);
24
- color: white;
24
+ color: var(--color--primary--opposite);
25
25
  }
26
26
 
27
27
  &.default {
28
- background-color: var(--color--background--overlay);
28
+ background-color: var(--color--background--element);
29
29
  color: var(--color--foreground);
30
30
 
31
31
  .icon {
@@ -10,23 +10,20 @@ interface Props {
10
10
  children?: ReactNode;
11
11
  className?: string;
12
12
  disabled?: boolean;
13
- id: string;
14
13
  name: string;
15
14
  onChange: ChangeEventHandler<HTMLInputElement>;
16
15
  }
17
16
 
18
- const Checkbox: FunctionComponent<Props> = ({ checked, children, className, disabled, id, name, onChange }) => (
17
+ const Checkbox: FunctionComponent<Props> = ({ checked, children, className, disabled, name, onChange }) => (
19
18
  <label
20
19
  className={classNames(styles.checkbox, className, {
21
20
  [styles.checked]: checked,
22
21
  })}
23
- htmlFor={id}
24
22
  >
25
23
  <input
26
24
  checked={checked}
27
25
  className={styles.input}
28
26
  disabled={disabled}
29
- id={id}
30
27
  name={name}
31
28
  type="checkbox"
32
29
  onChange={onChange}
@@ -27,6 +27,10 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
27
27
  >
28
28
  {typeof error !== 'undefined' && <EmptyState variant="error">{error.message}</EmptyState>}
29
29
 
30
+ {results.length === 0 && (
31
+ <EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
32
+ )}
33
+
30
34
  {results.map(({ definitions, exists, isAllowed, word }) => (
31
35
  <div
32
36
  className={classNames(styles.result, {
@@ -35,40 +39,34 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
35
39
  })}
36
40
  key={word}
37
41
  >
38
- {typeof word === 'undefined' && (
39
- <EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
40
- )}
41
-
42
- {typeof word !== 'undefined' && (
43
- <div className={styles.content}>
44
- {word && <h2 className={styles.word}>{word}</h2>}
42
+ <div className={styles.content}>
43
+ {word && <h2 className={styles.word}>{word}</h2>}
45
44
 
46
- {isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
45
+ {isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
47
46
 
48
- {isAllowed === true && (
49
- <>
50
- {definitions.length === 0 && (
51
- <>
52
- {exists && <div>{translate('dictionary.empty-state.no-definitions')}</div>}
53
- {!exists && <div>{translate('dictionary.empty-state.no-results')}</div>}
54
- </>
55
- )}
47
+ {isAllowed === true && (
48
+ <>
49
+ {definitions.length === 0 && (
50
+ <>
51
+ {exists && <div>{translate('dictionary.empty-state.no-definitions')}</div>}
52
+ {!exists && <div>{translate('dictionary.empty-state.no-results')}</div>}
53
+ </>
54
+ )}
56
55
 
57
- {definitions.length > 0 && (
58
- <ul className={styles.definitions}>
59
- {definitions.map((result, index) => (
60
- <li key={index} className={styles.definition}>
61
- {result}
62
- </li>
63
- ))}
64
- </ul>
65
- )}
66
- </>
67
- )}
56
+ {definitions.length > 0 && (
57
+ <ul className={styles.definitions}>
58
+ {definitions.map((result, index) => (
59
+ <li key={index} className={styles.definition}>
60
+ {result}
61
+ </li>
62
+ ))}
63
+ </ul>
64
+ )}
65
+ </>
66
+ )}
68
67
 
69
- {!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
70
- </div>
71
- )}
68
+ {!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
69
+ </div>
72
70
  </div>
73
71
  ))}
74
72
 
@@ -1,3 +1,4 @@
1
+ import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
1
2
  import classNames from 'classnames';
2
3
  import { ChangeEvent, FormEvent, FunctionComponent } from 'react';
3
4
  import { useDispatch } from 'react-redux';
@@ -28,8 +29,10 @@ const DictionaryInput: FunctionComponent<Props> = ({ className }) => {
28
29
  <form className={classNames(styles.dictionaryInput, className)} onSubmit={handleSubmit}>
29
30
  <input
30
31
  className={styles.input}
32
+ pattern={`.*[^\\s${COMMA_ARABIC}${COMMA_LATIN}].*`}
31
33
  placeholder={translate('dictionary.input.placeholder')}
32
34
  required
35
+ title={translate('dictionary.input.title')}
33
36
  type="text"
34
37
  value={input}
35
38
  onChange={handleChange}
@@ -8,7 +8,7 @@ $icon-size: 15px;
8
8
  min-width: $key-size;
9
9
  height: $key-size;
10
10
  padding: var(--spacing--s) var(--spacing--m);
11
- background-color: white;
11
+ background-color: var(--color--white);
12
12
  border: var(--border);
13
13
  border-radius: var(--border--radius);
14
14
  box-shadow: var(--box-shadow);
@@ -43,7 +43,7 @@ $hiding-duration: 200ms;
43
43
 
44
44
  .logoGrayscale {
45
45
  filter: grayscale(1);
46
- opacity: 0.3;
46
+ opacity: var(--opacity--disabled);
47
47
  }
48
48
 
49
49
  .logoColor {
@@ -60,12 +60,14 @@
60
60
  }
61
61
 
62
62
  .container {
63
+ --box-shadow--offset: 1px;
64
+ --box-shadow--offset--negative: calc(-1 * var(--box-shadow--offset));
65
+
63
66
  display: flex;
64
67
  flex-direction: column;
65
68
  height: 100%;
66
69
  background-color: var(--color--background);
67
- transition: var(--transition);
68
- transition-duration: var(--transition--duration--long);
70
+ transition: var(--transition--long);
69
71
  transform: translateX(var(--modal--width));
70
72
  opacity: 0;
71
73
 
@@ -9,4 +9,8 @@
9
9
  @include focus-effect;
10
10
 
11
11
  --background-color: var(--color--background);
12
+
13
+ &:focus-within {
14
+ z-index: 1;
15
+ }
12
16
  }
@@ -8,24 +8,21 @@ interface Props {
8
8
  children?: ReactNode;
9
9
  className?: string;
10
10
  disabled?: boolean;
11
- id: string;
12
11
  name: string;
13
12
  value: string;
14
13
  onChange: ChangeEventHandler<HTMLInputElement>;
15
14
  }
16
15
 
17
- const Radio: FunctionComponent<Props> = ({ checked, children, className, disabled, id, name, value, onChange }) => (
16
+ const Radio: FunctionComponent<Props> = ({ checked, children, className, disabled, name, value, onChange }) => (
18
17
  <label
19
18
  className={classNames(styles.radio, className, {
20
19
  [styles.checked]: checked,
21
20
  })}
22
- htmlFor={id}
23
21
  >
24
22
  <input
25
23
  checked={checked}
26
24
  className={styles.input}
27
25
  disabled={disabled}
28
- id={id}
29
26
  name={name}
30
27
  type="radio"
31
28
  value={value}
@@ -45,7 +45,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
45
45
  &:focus,
46
46
  &:hover {
47
47
  background-color: var(--color--primary);
48
- color: white;
48
+ color: var(--color--primary--opposite);
49
49
  }
50
50
  }
51
51
 
@@ -73,7 +73,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
73
73
  &.highlighted {
74
74
  &:not(:disabled) {
75
75
  background-color: var(--color--primary);
76
- color: white;
76
+ color: var(--color--primary--opposite);
77
77
  }
78
78
  }
79
79
  }
@@ -0,0 +1,19 @@
1
+ import { CSSProperties, FunctionComponent } from 'react';
2
+
3
+ const INVISIBLE_STYLE: CSSProperties = {
4
+ color: 'transparent',
5
+ pointerEvents: 'none',
6
+ position: 'absolute',
7
+ userSelect: 'none',
8
+ transform: 'translateY(-9999px)',
9
+ };
10
+
11
+ const SeoMessage: FunctionComponent = () => (
12
+ <p style={INVISIBLE_STYLE}>
13
+ Scrabble Solver 2 is a free and open-source analysis tool for Scrabble and Literaki. Quickly find top scoring words
14
+ using given letters and board state. Available in English, French, German, Polish & Spanish. Source code is
15
+ available on GitHub - contributions are welcome!
16
+ </p>
17
+ );
18
+
19
+ export default SeoMessage;
@@ -0,0 +1 @@
1
+ export { default } from './SeoMessage';
@@ -4,7 +4,7 @@
4
4
  width: 100%;
5
5
  border: var(--border);
6
6
  border-radius: var(--border--radius);
7
- background-color: white;
7
+ background-color: var(--color--white);
8
8
  text-align: center;
9
9
  }
10
10
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  .resultCandidatePicker {
4
4
  display: flex;
5
- align-items: center;
6
5
  gap: var(--spacing--l);
7
6
 
8
7
  @include media('<xs') {
@@ -14,6 +13,14 @@
14
13
  flex: 0 0 auto;
15
14
  display: flex;
16
15
  border-radius: var(--border--radius);
16
+
17
+ &.bothEnabled {
18
+ box-shadow: var(--box-shadow);
19
+
20
+ .button {
21
+ box-shadow: none;
22
+ }
23
+ }
17
24
  }
18
25
 
19
26
  .button {
@@ -50,7 +57,7 @@
50
57
  width: 100%;
51
58
  border: var(--border);
52
59
  border-radius: var(--border--radius);
53
- background-color: white;
60
+ background-color: var(--color--white);
54
61
  box-shadow: var(--box-shadow);
55
62
  cursor: pointer;
56
63
 
@@ -34,6 +34,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
34
34
  const disabled = isOutdated || !resultCandidate;
35
35
  const isPreviousDisabled = index <= 0 || disabled;
36
36
  const isNextDisabled = index >= results.length - 1 || disabled;
37
+ const bothEnabled = !isPreviousDisabled && !isNextDisabled;
37
38
 
38
39
  const handleNextClick = () => {
39
40
  if (!isNextDisabled) {
@@ -51,7 +52,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
51
52
 
52
53
  return (
53
54
  <div className={classNames(styles.resultCandidatePicker, className)} {...props}>
54
- <div className={styles.buttons}>
55
+ <div className={classNames(styles.buttons, { [styles.bothEnabled]: bothEnabled })}>
55
56
  <Button
56
57
  aria-label={translate('common.previous')}
57
58
  className={styles.button}