@mblinkov/whats-missing 20.0.46 → 20.0.48
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.
- package/CHANGELOG.md +503 -1
- package/dist/whats-missing.cjs.js +3 -3
- package/dist/whats-missing.es.js +213 -210
- package/package.json +1 -1
- package/src/Game.tsx +208 -180
package/package.json
CHANGED
package/src/Game.tsx
CHANGED
|
@@ -92,6 +92,11 @@ export default function Game({
|
|
|
92
92
|
if (style) {
|
|
93
93
|
style.remove();
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
// ✅ Cancel any ongoing preloads
|
|
97
|
+
if (abortController.current) {
|
|
98
|
+
abortController.current.abort();
|
|
99
|
+
}
|
|
95
100
|
};
|
|
96
101
|
}, []);
|
|
97
102
|
|
|
@@ -126,8 +131,10 @@ export default function Game({
|
|
|
126
131
|
const [isIPadProPortrait, setIsIPadProPortrait] = useState(false);
|
|
127
132
|
const [isIPadProLandscape, setIsIPadProLandscape] = useState(false);
|
|
128
133
|
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
|
|
135
|
+
// ✅ Система предзагрузки изображений
|
|
136
|
+
const preloadedUrls = useRef<Set<string>>(new Set());
|
|
137
|
+
const abortController = useRef<AbortController | null>(null);
|
|
131
138
|
|
|
132
139
|
// ✅ адаптив под мобилки, планшеты и десктоп
|
|
133
140
|
useEffect(() => {
|
|
@@ -137,6 +144,7 @@ export default function Game({
|
|
|
137
144
|
const mobile = width < 768 || (width === 926 && height === 428) || (width === 932 && height === 430); // iPhone 14 Pro Max в landscape тоже считается мобильным
|
|
138
145
|
const isLandscape = (width > height && mobile) || (width === 926 && height === 428) || (width === 932 && height === 430); // iPhone 14 Pro Max в landscape
|
|
139
146
|
const isSmallHeight = height < 700; // Nest Hub, маленькие экраны
|
|
147
|
+
const isWideScreen = width / height > 1.8; // ✅ Широкие экраны
|
|
140
148
|
|
|
141
149
|
// Определяем iPhone до 14 Pro Max в landscape режиме
|
|
142
150
|
// iPhone 14 Pro Max: 926x428 (landscape)
|
|
@@ -203,7 +211,9 @@ export default function Game({
|
|
|
203
211
|
// ✅ Вычисляем горизонтальный layout ОДИН РАЗ
|
|
204
212
|
const isHorizontal =
|
|
205
213
|
(mobile && width > height) ||
|
|
214
|
+
mobile || // ✅ ВСЕ мобильные устройства (включая portrait)
|
|
206
215
|
height < 700 ||
|
|
216
|
+
isWideScreen || // ✅ Широкие экраны
|
|
207
217
|
(width === 1366 && height === 766) ||
|
|
208
218
|
(width === 1366 && height === 768) ||
|
|
209
219
|
(width === 1280 && height === 720) ||
|
|
@@ -233,6 +243,15 @@ export default function Game({
|
|
|
233
243
|
// Маленькие экраны: используем переданный gameCubeSize или полную высоту
|
|
234
244
|
setContainerSize(gameCubeSize && gameCubeSize >= 400 ? gameCubeSize : null);
|
|
235
245
|
setScale(1);
|
|
246
|
+
} else if (isWideScreen) {
|
|
247
|
+
// ✅ Широкие экраны: используем стандартный размер но уменьшаем масштаб картинок
|
|
248
|
+
const minSize = 400;
|
|
249
|
+
const maxSize = 1200;
|
|
250
|
+
const finalSize = gameCubeSize
|
|
251
|
+
? Math.max(minSize, Math.min(maxSize, gameCubeSize))
|
|
252
|
+
: Math.min(1000, Math.min(width, height) * 0.9);
|
|
253
|
+
setContainerSize(finalSize);
|
|
254
|
+
setScale(0.85); // Уменьшаем масштаб картинок на 15%
|
|
236
255
|
} else {
|
|
237
256
|
// Десктопы: используем переданный gameCubeSize с разумными ограничениями
|
|
238
257
|
const minSize = 400;
|
|
@@ -252,33 +271,46 @@ export default function Game({
|
|
|
252
271
|
const getRandomSix = (arr: ImageItem[]) =>
|
|
253
272
|
[...arr].sort(() => Math.random() - 0.5).slice(0, 6);
|
|
254
273
|
|
|
255
|
-
// ✅
|
|
274
|
+
// ✅ Утилиты предзагрузки
|
|
256
275
|
const preloadImage = (src: string): Promise<void> => {
|
|
257
|
-
return new Promise((resolve
|
|
276
|
+
return new Promise((resolve) => {
|
|
258
277
|
const img = new Image();
|
|
259
|
-
img.onload = () =>
|
|
260
|
-
|
|
261
|
-
resolve();
|
|
262
|
-
};
|
|
263
|
-
img.onerror = reject;
|
|
278
|
+
img.onload = () => resolve();
|
|
279
|
+
img.onerror = () => resolve(); // Continue even on error
|
|
264
280
|
img.src = src;
|
|
265
281
|
});
|
|
266
282
|
};
|
|
267
283
|
|
|
268
|
-
const preloadImages = async (urls: string[]) => {
|
|
269
|
-
// Загружаем с задержкой, чтобы не блокировать UI
|
|
284
|
+
const preloadImages = async (urls: string[], signal?: AbortSignal): Promise<void> => {
|
|
270
285
|
const promises = urls.map(url => preloadImage(url));
|
|
271
286
|
await Promise.allSettled(promises);
|
|
272
287
|
};
|
|
273
288
|
|
|
274
|
-
|
|
275
|
-
|
|
289
|
+
// ✅ Функция предзагрузки темы
|
|
290
|
+
const startThemePreload = (themeName: Theme) => {
|
|
291
|
+
// Cancel previous preload
|
|
292
|
+
if (abortController.current) {
|
|
293
|
+
abortController.current.abort();
|
|
294
|
+
}
|
|
276
295
|
|
|
277
|
-
|
|
296
|
+
// Create new controller
|
|
297
|
+
abortController.current = new AbortController();
|
|
278
298
|
|
|
279
|
-
//
|
|
280
|
-
|
|
299
|
+
// Get theme images and start preloading
|
|
300
|
+
const themeImages = themes[themeName];
|
|
301
|
+
const urls = themeImages.map(item => item.src);
|
|
281
302
|
|
|
303
|
+
preloadImages(urls, abortController.current.signal).then(() => {
|
|
304
|
+
// Mark as preloaded
|
|
305
|
+
urls.forEach(url => preloadedUrls.current.add(url));
|
|
306
|
+
}).catch(() => {
|
|
307
|
+
// Ignore abort errors
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const startGame = () => {
|
|
312
|
+
if (!theme) return;
|
|
313
|
+
const selected = getRandomSix(themes[theme]);
|
|
282
314
|
setFiles(selected);
|
|
283
315
|
setStarted(true);
|
|
284
316
|
setFinished(false);
|
|
@@ -287,11 +319,13 @@ export default function Game({
|
|
|
287
319
|
setResultsTable([]);
|
|
288
320
|
setUsedHidden([]);
|
|
289
321
|
|
|
290
|
-
// ✅
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
322
|
+
// ✅ Preload current round images
|
|
323
|
+
const urls = selected.map(item => item.src);
|
|
324
|
+
preloadImages(urls, abortController.current?.signal).then(() => {
|
|
325
|
+
urls.forEach(url => preloadedUrls.current.add(url));
|
|
326
|
+
}).catch(() => {
|
|
327
|
+
// Ignore abort errors
|
|
328
|
+
});
|
|
295
329
|
|
|
296
330
|
startRound(selected, [], true);
|
|
297
331
|
};
|
|
@@ -313,9 +347,21 @@ export default function Game({
|
|
|
313
347
|
setInputValue("");
|
|
314
348
|
setTimeLeft(20);
|
|
315
349
|
setResult(null);
|
|
350
|
+
|
|
351
|
+
// ✅ Preload next round images in background
|
|
352
|
+
if (theme && !isFirst) {
|
|
353
|
+
const nextPool = getRandomSix(themes[theme].filter(item => !used.includes(item.name)));
|
|
354
|
+
const nextUrls = nextPool.map(item => item.src);
|
|
355
|
+
preloadImages(nextUrls, abortController.current?.signal).then(() => {
|
|
356
|
+
nextUrls.forEach(url => preloadedUrls.current.add(url));
|
|
357
|
+
}).catch(() => {
|
|
358
|
+
// Ignore abort errors
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
316
362
|
if (isFirst) {
|
|
317
363
|
setPhase("ready");
|
|
318
|
-
setReadyTime(
|
|
364
|
+
setReadyTime(5); // Увеличено с 3 до 5
|
|
319
365
|
setMemorizeTime(10);
|
|
320
366
|
} else {
|
|
321
367
|
setPhase("memorize");
|
|
@@ -398,20 +444,20 @@ export default function Game({
|
|
|
398
444
|
|
|
399
445
|
return (
|
|
400
446
|
// ensure logo is positioned inside the square container
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
447
|
+
<div style={{ ...styles.gmLogoFixed, position: "absolute", top: 16, left: 16, zIndex: 30 }}>
|
|
448
|
+
<picture>
|
|
449
|
+
<source
|
|
450
|
+
srcSet={window.origin + "/cloud/speakid/games/whatsmissing/logo.svg"}
|
|
451
|
+
type="image/svg+xml"
|
|
452
|
+
/>
|
|
453
|
+
<img
|
|
454
|
+
src={window.origin + "/cloud/speakid/games/whatsmissing/logo.png"}
|
|
455
|
+
alt="SPEAKID Logo"
|
|
456
|
+
style={styles.gmLogoImg}
|
|
457
|
+
loading="lazy"
|
|
458
|
+
/>
|
|
459
|
+
</picture>
|
|
460
|
+
</div>
|
|
415
461
|
);
|
|
416
462
|
},
|
|
417
463
|
[isMobile]
|
|
@@ -449,6 +495,7 @@ export default function Game({
|
|
|
449
495
|
boxShadow: isMobile ? "none" : "0 0 40px rgba(0,0,0,0.1)",
|
|
450
496
|
margin: isMobile ? "0 auto" : "unset",
|
|
451
497
|
position: "relative", // needed so absolute logo is inside the square
|
|
498
|
+
transform: `scale(${scale})`, // ✅ Применяем масштаб для широких экранов
|
|
452
499
|
}}
|
|
453
500
|
>
|
|
454
501
|
<div
|
|
@@ -462,62 +509,65 @@ export default function Game({
|
|
|
462
509
|
}}
|
|
463
510
|
>
|
|
464
511
|
<div id="whats-missing-root">
|
|
465
|
-
|
|
512
|
+
{!isMobile && MemoizedLogo}
|
|
466
513
|
{/* ====== ИГРОВАЯ ЛОГИКА ====== */}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
514
|
+
{!theme && !started && (
|
|
515
|
+
<div style={styles.gmCenterScreen}>
|
|
516
|
+
<h1 style={styles.gmHeadline1}>WHAT'S MISSING?</h1>
|
|
517
|
+
<p style={styles.gmBodyM}>Select a theme:</p>
|
|
471
518
|
<div style={{ display: "flex", gap: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "8px" : "16px" }}>
|
|
472
|
-
|
|
519
|
+
{["animals", "food", "toys"].map((t) => (
|
|
473
520
|
<button key={t} style={{
|
|
474
521
|
...styles.gmButton,
|
|
475
522
|
padding: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "8px 12px" : "12px 24px",
|
|
476
523
|
fontSize: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "12px" : "16px",
|
|
477
524
|
minWidth: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "70px" : "auto"
|
|
478
|
-
}} onClick={() =>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
525
|
+
}} onClick={() => {
|
|
526
|
+
setTheme(t as Theme);
|
|
527
|
+
startThemePreload(t as Theme);
|
|
528
|
+
}}>
|
|
529
|
+
{t === "animals" ? "🐶 Animals" : t === "food" ? "🍎 Food" : "🧸 Toys"}
|
|
530
|
+
</button>
|
|
531
|
+
))}
|
|
532
|
+
</div>
|
|
533
|
+
<div style={{ marginTop: 24 }}>
|
|
534
|
+
<p style={styles.gmBodyS}>Choose number of rounds:</p>
|
|
485
535
|
<div style={{ display: "flex", gap: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "6px" : "12px", marginTop: 8 }}>
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
536
|
+
{[3, 4, 5].map((n) => (
|
|
537
|
+
<button
|
|
538
|
+
key={n}
|
|
539
|
+
style={{
|
|
540
|
+
...styles.gmButton,
|
|
541
|
+
...(rounds === n ? styles.gmButtonActive : {}),
|
|
492
542
|
padding: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "6px 10px" : "12px 24px",
|
|
493
543
|
fontSize: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "12px" : "16px",
|
|
494
544
|
minWidth: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "40px" : "auto"
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
</div>
|
|
545
|
+
}}
|
|
546
|
+
onClick={() => setRounds(n)}
|
|
547
|
+
>
|
|
548
|
+
{n}
|
|
549
|
+
</button>
|
|
550
|
+
))}
|
|
502
551
|
</div>
|
|
503
552
|
</div>
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
553
|
+
</div>
|
|
554
|
+
)}
|
|
555
|
+
{theme && !started && (
|
|
556
|
+
<div style={styles.gmCenterScreen}>
|
|
557
|
+
<h1 style={styles.gmHeadline1}>Theme selected: {theme}</h1>
|
|
558
|
+
<p style={styles.gmBodyM}>Rounds: {rounds}</p>
|
|
509
559
|
<button style={{
|
|
510
560
|
...styles.gmButton,
|
|
511
561
|
padding: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "8px 16px" : "12px 24px",
|
|
512
562
|
fontSize: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "14px" : "16px",
|
|
513
563
|
minWidth: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "120px" : "auto"
|
|
514
564
|
}} onClick={startGame}>
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
565
|
+
▶ Start game
|
|
566
|
+
</button>
|
|
567
|
+
</div>
|
|
568
|
+
)}
|
|
569
|
+
{finished && (
|
|
570
|
+
<div style={styles.gmCenterScreen}>
|
|
521
571
|
<h1 style={{
|
|
522
572
|
...styles.gmHeadline1,
|
|
523
573
|
marginTop: (isMobile && window.innerWidth > window.innerHeight) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) || isIPadMiniPortrait || isIPadMiniLandscape || isIPadAirPortrait || isIPadAirLandscape || isSurfaceDuoPortrait || isSurfaceDuoLandscape || isIPadProPortrait || isIPadProLandscape ? "0px" : styles.gmHeadline1.marginTop,
|
|
@@ -541,31 +591,31 @@ export default function Game({
|
|
|
541
591
|
marginTop: (isMobile && window.innerWidth > window.innerHeight) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) || isIPadMiniPortrait || isIPadMiniLandscape || isIPadAirPortrait || isIPadAirLandscape || isSurfaceDuoPortrait || isSurfaceDuoLandscape || isIPadProPortrait || isIPadProLandscape ? "0px" : "20px",
|
|
542
592
|
marginBottom: (isMobile && window.innerWidth > window.innerHeight) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) || isIPadMiniPortrait || isIPadMiniLandscape || isIPadAirPortrait || isIPadAirLandscape || isSurfaceDuoPortrait || isSurfaceDuoLandscape || isIPadProPortrait || isIPadProLandscape ? "4px" : "32px"
|
|
543
593
|
}}>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
594
|
+
<thead>
|
|
595
|
+
<tr>
|
|
596
|
+
<th>Round</th>
|
|
597
|
+
<th>Your Answer</th>
|
|
598
|
+
<th>Correct</th>
|
|
599
|
+
<th>Result</th>
|
|
600
|
+
</tr>
|
|
601
|
+
</thead>
|
|
602
|
+
<tbody>
|
|
603
|
+
{resultsTable.map((r, i) => (
|
|
604
|
+
<tr key={i}>
|
|
605
|
+
<td style={styles.gmTableCell}>{r.round}</td>
|
|
606
|
+
<td style={styles.gmTableCell}>{r.answer || "—"}</td>
|
|
607
|
+
<td style={styles.gmTableCell}>{r.correct}</td>
|
|
608
|
+
<td style={styles.gmTableCell}>
|
|
609
|
+
{r.result === "correct"
|
|
610
|
+
? "✔ Correct"
|
|
611
|
+
: r.result === "almost"
|
|
612
|
+
? "◐ Almost (0.5)"
|
|
613
|
+
: "✘ Wrong"}
|
|
614
|
+
</td>
|
|
550
615
|
</tr>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
<tr key={i}>
|
|
555
|
-
<td style={styles.gmTableCell}>{r.round}</td>
|
|
556
|
-
<td style={styles.gmTableCell}>{r.answer || "—"}</td>
|
|
557
|
-
<td style={styles.gmTableCell}>{r.correct}</td>
|
|
558
|
-
<td style={styles.gmTableCell}>
|
|
559
|
-
{r.result === "correct"
|
|
560
|
-
? "✔ Correct"
|
|
561
|
-
: r.result === "almost"
|
|
562
|
-
? "◐ Almost (0.5)"
|
|
563
|
-
: "✘ Wrong"}
|
|
564
|
-
</td>
|
|
565
|
-
</tr>
|
|
566
|
-
))}
|
|
567
|
-
</tbody>
|
|
568
|
-
</table>
|
|
616
|
+
))}
|
|
617
|
+
</tbody>
|
|
618
|
+
</table>
|
|
569
619
|
<div style={{
|
|
570
620
|
display: "flex",
|
|
571
621
|
gap: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "6px" : "12px",
|
|
@@ -577,23 +627,23 @@ export default function Game({
|
|
|
577
627
|
fontSize: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "12px" : "16px",
|
|
578
628
|
minWidth: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "80px" : "auto"
|
|
579
629
|
}} onClick={startGame}>
|
|
580
|
-
|
|
581
|
-
|
|
630
|
+
🔁 Play again
|
|
631
|
+
</button>
|
|
582
632
|
<button style={{
|
|
583
633
|
...styles.gmButton,
|
|
584
634
|
padding: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "6px 10px" : "12px 24px",
|
|
585
635
|
fontSize: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "12px" : "16px",
|
|
586
636
|
minWidth: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "80px" : "auto"
|
|
587
637
|
}} onClick={exitGame}>
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
</div>
|
|
638
|
+
⬅️ Choose theme
|
|
639
|
+
</button>
|
|
591
640
|
</div>
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
641
|
+
</div>
|
|
642
|
+
)}
|
|
643
|
+
{started && !finished && (
|
|
644
|
+
<div style={styles.gmGameLayout}>
|
|
645
|
+
<div
|
|
646
|
+
style={{
|
|
597
647
|
minHeight: isMobile && window.innerWidth <= 375 && window.innerHeight <= 667
|
|
598
648
|
? "45px" // iPhone SE: еще более компактная высота
|
|
599
649
|
: (isMobile && window.innerWidth > window.innerHeight && window.innerHeight <= 428)
|
|
@@ -607,10 +657,10 @@ export default function Game({
|
|
|
607
657
|
: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700
|
|
608
658
|
? "60px" // iPhone landscape и малые экраны: компактная высота
|
|
609
659
|
: "160px",
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
660
|
+
display: "flex",
|
|
661
|
+
flexDirection: "column",
|
|
662
|
+
justifyContent: "center",
|
|
663
|
+
alignItems: "center",
|
|
614
664
|
paddingTop: isMobile && window.innerWidth <= 375 && window.innerHeight <= 667
|
|
615
665
|
? "8px" // iPhone SE: поднимаем таймер еще выше
|
|
616
666
|
: (isMobile && window.innerWidth > window.innerHeight && window.innerHeight <= 428)
|
|
@@ -624,23 +674,23 @@ export default function Game({
|
|
|
624
674
|
: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700
|
|
625
675
|
? "15px" // iPhone landscape и малые экраны: поднимаем выше
|
|
626
676
|
: "40px"
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
677
|
+
}}
|
|
678
|
+
>
|
|
679
|
+
{phase === "ready" && (
|
|
680
|
+
<>
|
|
681
|
+
<h1 style={{ ...styles.gmHeadline1, color: "#ec4c44" }}>GET READY</h1>
|
|
682
|
+
<div style={styles.gmHourglass}>⏳</div>
|
|
683
|
+
</>
|
|
684
|
+
)}
|
|
685
|
+
{phase === "memorize" && (
|
|
686
|
+
<p style={{ ...styles.gmBodyM, color: "#10b981" }}>
|
|
687
|
+
MEMORIZE ({memorizeTime})
|
|
688
|
+
</p>
|
|
689
|
+
)}
|
|
690
|
+
{phase === "guess" && !answered && (
|
|
691
|
+
<p style={styles.gmBodyM}>⏳ Time left: {timeLeft}s</p>
|
|
692
|
+
)}
|
|
693
|
+
</div>
|
|
644
694
|
{phase !== "ready" && (
|
|
645
695
|
<div
|
|
646
696
|
style={{
|
|
@@ -770,37 +820,15 @@ export default function Game({
|
|
|
770
820
|
}}
|
|
771
821
|
>
|
|
772
822
|
{!isHidden && (
|
|
773
|
-
<
|
|
774
|
-
{
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
backgroundSize: "200% 100%",
|
|
783
|
-
borderRadius: "inherit",
|
|
784
|
-
display: "flex",
|
|
785
|
-
justifyContent: "center",
|
|
786
|
-
alignItems: "center"
|
|
787
|
-
}} />
|
|
788
|
-
)}
|
|
789
|
-
<img
|
|
790
|
-
src={file.src}
|
|
791
|
-
alt={file.name}
|
|
792
|
-
// @ts-ignore - fetchpriority is a newer API
|
|
793
|
-
fetchpriority={loadedImages.has(file.src) ? "auto" : "high"}
|
|
794
|
-
onLoad={() => setLoadedImages(prev => new Set(prev).add(file.src))}
|
|
795
|
-
style={{
|
|
796
|
-
width: "100%",
|
|
797
|
-
height: "100%",
|
|
798
|
-
objectFit: "cover",
|
|
799
|
-
opacity: loadedImages.has(file.src) ? 1 : 0,
|
|
800
|
-
transition: "opacity 0.3s ease"
|
|
801
|
-
}}
|
|
802
|
-
/>
|
|
803
|
-
</div>
|
|
823
|
+
<img
|
|
824
|
+
src={file.src}
|
|
825
|
+
alt={file.name}
|
|
826
|
+
style={{
|
|
827
|
+
width: "100%",
|
|
828
|
+
height: "100%",
|
|
829
|
+
objectFit: "cover",
|
|
830
|
+
}}
|
|
831
|
+
/>
|
|
804
832
|
)}
|
|
805
833
|
</div>
|
|
806
834
|
);
|
|
@@ -835,7 +863,7 @@ export default function Game({
|
|
|
835
863
|
? "50px" // iPhone landscape и малые экраны: компактная высота
|
|
836
864
|
: "80px"
|
|
837
865
|
}}>
|
|
838
|
-
|
|
866
|
+
{phase === "guess" && !answered && (
|
|
839
867
|
<div style={{
|
|
840
868
|
display: "flex",
|
|
841
869
|
flexDirection: isHorizontalLayout ? "row" : "column",
|
|
@@ -890,11 +918,11 @@ export default function Game({
|
|
|
890
918
|
: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 ? "400px" : "300px"
|
|
891
919
|
}}>
|
|
892
920
|
<>
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
921
|
+
<input
|
|
922
|
+
type="text"
|
|
923
|
+
placeholder="Type the missing word"
|
|
924
|
+
value={inputValue}
|
|
925
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
898
926
|
style={{
|
|
899
927
|
...styles.gmInput,
|
|
900
928
|
width: isMobile && window.innerWidth <= 375 && window.innerHeight <= 667
|
|
@@ -968,8 +996,8 @@ export default function Game({
|
|
|
968
996
|
: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 ? "14px" : "16px",
|
|
969
997
|
flex: isHorizontalLayout ? "1" : "none"
|
|
970
998
|
}}
|
|
971
|
-
|
|
972
|
-
|
|
999
|
+
/>
|
|
1000
|
+
<button
|
|
973
1001
|
style={{
|
|
974
1002
|
...styles.gmButton,
|
|
975
1003
|
marginLeft: isHorizontalLayout ? "8px" : "0",
|
|
@@ -1044,15 +1072,15 @@ export default function Game({
|
|
|
1044
1072
|
: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 ? "80px" : "100px",
|
|
1045
1073
|
flexShrink: 0
|
|
1046
1074
|
}}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1075
|
+
onClick={checkAnswer}
|
|
1076
|
+
disabled={animating}
|
|
1077
|
+
>
|
|
1078
|
+
{animating ? "..." : "Check"}
|
|
1079
|
+
</button>
|
|
1052
1080
|
</>
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1081
|
+
</div>
|
|
1082
|
+
)}
|
|
1083
|
+
{answered && (
|
|
1056
1084
|
<button style={{
|
|
1057
1085
|
...styles.gmButton,
|
|
1058
1086
|
padding: isMobile && window.innerWidth <= 375 && window.innerHeight <= 667
|
|
@@ -1102,12 +1130,12 @@ export default function Game({
|
|
|
1102
1130
|
? "14px" // iPad и Surface DUO: размер шрифта кнопки Next round
|
|
1103
1131
|
: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 ? "14px" : "16px"
|
|
1104
1132
|
}} onClick={nextRound}>
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
</div>
|
|
1133
|
+
Next round
|
|
1134
|
+
</button>
|
|
1135
|
+
)}
|
|
1109
1136
|
</div>
|
|
1110
|
-
|
|
1137
|
+
</div>
|
|
1138
|
+
)}
|
|
1111
1139
|
</div>
|
|
1112
1140
|
</div>
|
|
1113
1141
|
</div>
|