@mblinkov/whats-missing 20.0.45 → 20.0.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblinkov/whats-missing",
3
- "version": "20.0.45",
3
+ "version": "20.0.46",
4
4
  "main": "dist/whats-missing.umd.js",
5
5
  "module": "dist/whats-missing.es.js",
6
6
  "types": "dist/index.d.ts",
package/src/Game.tsx CHANGED
@@ -14,6 +14,8 @@ type RoundResult = {
14
14
  // ✅ базовый reset
15
15
  const globalReset = () => {
16
16
  const style = document.createElement("style");
17
+ style.id = "whats-missing-reset"; // ✅ Добавляем ID для удаления
18
+
17
19
  style.textContent = `
18
20
  #whats-missing-root, #whats-missing-root * {
19
21
  box-sizing: border-box;
@@ -41,6 +43,13 @@ const globalReset = () => {
41
43
  overflow: hidden !important;
42
44
  }
43
45
  `;
46
+
47
+ // ✅ Удаляем старый стиль, если он есть
48
+ const existingStyle = document.getElementById("whats-missing-reset");
49
+ if (existingStyle) {
50
+ existingStyle.remove();
51
+ }
52
+
44
53
  document.head.appendChild(style);
45
54
  };
46
55
 
@@ -74,7 +83,15 @@ export default function Game({
74
83
  useEffect(() => {
75
84
  globalReset();
76
85
  return () => {
86
+ // ✅ Восстанавливаем overflow на html и body
87
+ document.documentElement.style.overflow = "";
77
88
  document.body.style.overflow = "";
89
+
90
+ // ✅ Удаляем наш style элемент
91
+ const style = document.getElementById("whats-missing-reset");
92
+ if (style) {
93
+ style.remove();
94
+ }
78
95
  };
79
96
  }, []);
80
97
 
@@ -108,6 +125,9 @@ export default function Game({
108
125
  const [isSurfaceDuoLandscape, setIsSurfaceDuoLandscape] = useState(false);
109
126
  const [isIPadProPortrait, setIsIPadProPortrait] = useState(false);
110
127
  const [isIPadProLandscape, setIsIPadProLandscape] = useState(false);
128
+ const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
129
+ const [loadedImages, setLoadedImages] = useState<Set<string>>(new Set());
130
+ const [preloadQueue, setPreloadQueue] = useState<string[]>([]);
111
131
 
112
132
  // ✅ адаптив под мобилки, планшеты и десктоп
113
133
  useEffect(() => {
@@ -180,6 +200,25 @@ export default function Game({
180
200
  setIsIPadProPortrait(isIPadProPortrait);
181
201
  setIsIPadProLandscape(isIPadProLandscape);
182
202
 
203
+ // ✅ Вычисляем горизонтальный layout ОДИН РАЗ
204
+ const isHorizontal =
205
+ (mobile && width > height) ||
206
+ height < 700 ||
207
+ (width === 1366 && height === 766) ||
208
+ (width === 1366 && height === 768) ||
209
+ (width === 1280 && height === 720) ||
210
+ (width === 1440 && height === 900) ||
211
+ isIPadMiniPortrait ||
212
+ isIPadMiniLandscape ||
213
+ isIPadAirPortrait ||
214
+ isIPadAirLandscape ||
215
+ isSurfaceDuoPortrait ||
216
+ isSurfaceDuoLandscape ||
217
+ isIPadProPortrait ||
218
+ isIPadProLandscape ||
219
+ isDesktopLayout;
220
+ setIsHorizontalLayout(isHorizontal);
221
+
183
222
  const isOldIPhoneLandscape = isLandscape && height <= 428; // до iPhone 14 Pro Max включительно
184
223
 
185
224
  // treat only phones as "mobile" so tablets use the desktop (square/centered) layout
@@ -213,9 +252,33 @@ export default function Game({
213
252
  const getRandomSix = (arr: ImageItem[]) =>
214
253
  [...arr].sort(() => Math.random() - 0.5).slice(0, 6);
215
254
 
216
- const startGame = () => {
255
+ // Функции предзагрузки изображений
256
+ const preloadImage = (src: string): Promise<void> => {
257
+ return new Promise((resolve, reject) => {
258
+ const img = new Image();
259
+ img.onload = () => {
260
+ setLoadedImages(prev => new Set(prev).add(src));
261
+ resolve();
262
+ };
263
+ img.onerror = reject;
264
+ img.src = src;
265
+ });
266
+ };
267
+
268
+ const preloadImages = async (urls: string[]) => {
269
+ // Загружаем с задержкой, чтобы не блокировать UI
270
+ const promises = urls.map(url => preloadImage(url));
271
+ await Promise.allSettled(promises);
272
+ };
273
+
274
+ const startGame = async () => {
217
275
  if (!theme) return;
276
+
218
277
  const selected = getRandomSix(themes[theme]);
278
+
279
+ // ✅ Предзагружаем текущие изображения с высоким приоритетом
280
+ await preloadImages(selected.map(item => item.src));
281
+
219
282
  setFiles(selected);
220
283
  setStarted(true);
221
284
  setFinished(false);
@@ -223,6 +286,13 @@ export default function Game({
223
286
  setScore(0);
224
287
  setResultsTable([]);
225
288
  setUsedHidden([]);
289
+
290
+ // ✅ Загружаем следующий раунд в фоне
291
+ const nextBatch = getRandomSix(
292
+ themes[theme].filter(item => !selected.includes(item))
293
+ );
294
+ preloadImages(nextBatch.map(item => item.src)); // Не ждем завершения
295
+
226
296
  startRound(selected, [], true);
227
297
  };
228
298
 
@@ -598,6 +668,8 @@ export default function Game({
598
668
  ? "150px" // mobile portrait: стандартная высота
599
669
  : window.innerHeight < 700
600
670
  ? "140px" // Nest Hub: компактная высота
671
+ : (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5) // Среднее масштабирование (125%-150%)
672
+ ? "240px" // Больше высота для экранов с масштабированием
601
673
  : "210px", // desktop: стандартная высота
602
674
  gap: isMobile && window.innerWidth <= 375 && window.innerHeight <= 667
603
675
  ? "12px" // iPhone SE: больше отступов
@@ -607,7 +679,11 @@ export default function Game({
607
679
  ? "12px" // iPhone 12 Pro в landscape: такие же отступы как SE
608
680
  : (window.innerWidth === 926 && window.innerHeight === 428) // iPhone 14 Pro Max
609
681
  ? "12px" // iPhone 14 Pro Max в landscape: такие же отступы как SE
610
- : isMobile || window.innerHeight < 700 ? "8px" : "20px",
682
+ : isMobile || window.innerHeight < 700
683
+ ? "8px"
684
+ : (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5) // Среднее масштабирование (125%-150%)
685
+ ? "24px" // Больше отступы для экранов с масштабированием
686
+ : "20px",
611
687
  justifyItems: "center",
612
688
  maxWidth: isMobile && window.innerWidth > window.innerHeight
613
689
  ? "90%" // mobile landscape: используем больше ширины
@@ -626,8 +702,10 @@ export default function Game({
626
702
  ? "8px" // iPhone XR в landscape: минимальные отступы как SE
627
703
  : (window.innerWidth === 844 && window.innerHeight === 390) // iPhone 12 Pro
628
704
  ? "8px" // iPhone 12 Pro в landscape: минимальные отступы как SE
629
- : (window.innerWidth === 926 && window.innerHeight === 428) // iPhone 14 Pro Max
705
+ : (window.innerWidth === 926 && window.innerHeight === 428) // iPhone 14 Pro Max
630
706
  ? "8px" // iPhone 14 Pro Max в landscape: минимальные отступы как SE
707
+ : (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5) // Среднее масштабирование (125%-150%)
708
+ ? "20px" // Дополнительные отступы для экранов с масштабированием
631
709
  : "16px",
632
710
  }}
633
711
  >
@@ -692,15 +770,37 @@ export default function Game({
692
770
  }}
693
771
  >
694
772
  {!isHidden && (
695
- <img
696
- src={file.src}
697
- alt={file.name}
698
- style={{
699
- width: "100%",
700
- height: "100%",
701
- objectFit: "cover",
702
- }}
703
- />
773
+ <div style={{ position: "relative", width: "100%", height: "100%" }}>
774
+ {!loadedImages.has(file.src) && (
775
+ <div style={{
776
+ position: "absolute",
777
+ top: 0,
778
+ left: 0,
779
+ width: "100%",
780
+ height: "100%",
781
+ background: "linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)",
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>
704
804
  )}
705
805
  </div>
706
806
  );
@@ -738,7 +838,7 @@ export default function Game({
738
838
  {phase === "guess" && !answered && (
739
839
  <div style={{
740
840
  display: "flex",
741
- flexDirection: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 || (window.innerWidth === 1366 && window.innerHeight === 766) || (window.innerWidth === 1366 && window.innerHeight === 768) || (window.innerWidth === 1280 && window.innerHeight === 720) || (window.innerWidth === 1440 && window.innerHeight === 900) || isIPadMiniPortrait || isIPadMiniLandscape || isIPadAirPortrait || isIPadAirLandscape || isSurfaceDuoPortrait || isSurfaceDuoLandscape || isIPadProPortrait || isIPadProLandscape || isDesktopLayout ? "row" : "column",
841
+ flexDirection: isHorizontalLayout ? "row" : "column",
742
842
  gap: isMobile && window.innerWidth <= 375 && window.innerHeight <= 667
743
843
  ? "3px" // iPhone SE: еще более минимальный отступ
744
844
  : (isMobile && window.innerWidth > window.innerHeight && window.innerHeight <= 428)
@@ -866,13 +966,13 @@ export default function Game({
866
966
  : isIPadMiniPortrait || isIPadMiniLandscape || isIPadAirPortrait || isIPadAirLandscape || isSurfaceDuoPortrait || isSurfaceDuoLandscape || isIPadProPortrait || isIPadProLandscape
867
967
  ? "14px" // iPad и Surface DUO: размер шрифта инпута
868
968
  : (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 ? "14px" : "16px",
869
- flex: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 || (window.innerWidth === 1366 && window.innerHeight === 766) || (window.innerWidth === 1366 && window.innerHeight === 768) || (window.innerWidth === 1280 && window.innerHeight === 720) || (window.innerWidth === 1440 && window.innerHeight === 900) || isIPadMiniPortrait || isIPadMiniLandscape || isIPadAirPortrait || isIPadAirLandscape || isSurfaceDuoPortrait || isSurfaceDuoLandscape || isIPadProPortrait || isIPadProLandscape || isDesktopLayout ? "1" : "none"
969
+ flex: isHorizontalLayout ? "1" : "none"
870
970
  }}
871
971
  />
872
972
  <button
873
973
  style={{
874
974
  ...styles.gmButton,
875
- marginLeft: (isMobile && window.innerWidth > window.innerHeight) || window.innerHeight < 700 || (window.innerWidth === 1366 && window.innerHeight === 766) || (window.innerWidth === 1366 && window.innerHeight === 768) || (window.innerWidth === 1280 && window.innerHeight === 720) || (window.innerWidth === 1440 && window.innerHeight === 900) || isIPadMiniPortrait || isIPadMiniLandscape || isIPadAirPortrait || isIPadAirLandscape || isSurfaceDuoPortrait || isSurfaceDuoLandscape || isIPadProPortrait || isIPadProLandscape || isDesktopLayout ? "8px" : "0",
975
+ marginLeft: isHorizontalLayout ? "8px" : "0",
876
976
  padding: isMobile && window.innerWidth <= 375 && window.innerHeight <= 667
877
977
  ? "5px 8px" // iPhone SE: еще более компактный padding
878
978
  : (isMobile && window.innerWidth > window.innerHeight && window.innerHeight <= 428)