@hkdigital/lib-core 0.5.10 → 0.5.11

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.
@@ -2,6 +2,25 @@ const DEFAULT_WIDTHS = [1920, 1536, 1024, 640];
2
2
 
3
3
  const DEFAULT_THUMBNAIL_WIDTH = 150;
4
4
 
5
+ const FAVICON_SIZES = [
6
+ 16, // classic browser tab icon
7
+ 32, // high-resolution browser support
8
+ 48, // Windows desktop shortcuts
9
+ 120, // iPhone older retina
10
+ 152, // iPad retina, iOS Safari bookmarks
11
+ 167, // iPad Pro
12
+ 180, // iPhone retina, iOS home screen
13
+ 192, // Android home screen, Chrome PWA
14
+ 512 // PWA application icon, Android splash
15
+ ];
16
+
17
+ const APPLE_TOUCH_SIZES = [
18
+ 120, // iPhone older retina
19
+ 152, // iPad retina, iOS Safari bookmarks
20
+ 167, // iPad Pro
21
+ 180 // iPhone retina, iOS home screen
22
+ ];
23
+
5
24
  const DEFAULT_PRESETS = {
6
25
  default: {
7
26
  format: 'avif',
@@ -74,9 +93,15 @@ export function generateResponseConfigs(options) {
74
93
 
75
94
  // @ts-ignore
76
95
  const responsiveConfig = entries.find(([key]) => key === 'responsive');
96
+ // @ts-ignore
97
+ const faviconsConfig = entries.find(([key]) => key === 'favicons');
98
+ // @ts-ignore
99
+ const appleTouchIconsConfig = entries.find(([key]) => key === 'apple-touch-icons');
77
100
  // console.log('responsiveConfig found:', !!responsiveConfig);
78
101
 
79
102
  const widths = options?.widths ?? DEFAULT_WIDTHS;
103
+ const faviconSizes = options?.faviconSizes ?? FAVICON_SIZES;
104
+ const appleTouchSizes = options?.appleTouchSizes ?? APPLE_TOUCH_SIZES;
80
105
 
81
106
  // Always include the main image(s) and a thumbnail version
82
107
  const thumbnailConfig = {
@@ -84,6 +109,32 @@ export function generateResponseConfigs(options) {
84
109
  w: String(options?.thumbnailWidth ?? DEFAULT_THUMBNAIL_WIDTH)
85
110
  };
86
111
 
112
+ // Handle favicons directive - generate all favicon sizes as PNG
113
+ if (faviconsConfig) {
114
+ const faviconConfigs = faviconSizes.map((w) => {
115
+ return {
116
+ ...configPairs,
117
+ w: String(w),
118
+ format: 'png'
119
+ };
120
+ });
121
+ // console.log('Returning favicon configs:', faviconConfigs);
122
+ return faviconConfigs;
123
+ }
124
+
125
+ // Handle apple-touch-icons directive - generate Apple touch icon sizes as PNG
126
+ if (appleTouchIconsConfig) {
127
+ const appleTouchConfigs = appleTouchSizes.map((w) => {
128
+ return {
129
+ ...configPairs,
130
+ w: String(w),
131
+ format: 'png'
132
+ };
133
+ });
134
+ // console.log('Returning apple-touch-icon configs:', appleTouchConfigs);
135
+ return appleTouchConfigs;
136
+ }
137
+
87
138
  if (!responsiveConfig) {
88
139
  // Directive 'responsive' was not set => return original + thumbnail
89
140
  const originalConfig = configPairs; // No 'w' means original dimensions
@@ -129,6 +180,18 @@ export function generateDefaultDirectives(options) {
129
180
  params.set('as', 'metadata');
130
181
  }
131
182
 
183
+ // > Return metadata if directive 'favicons' is set
184
+
185
+ if (params.has('favicons')) {
186
+ params.set('as', 'metadata');
187
+ }
188
+
189
+ // > Return metadata if directive 'apple-touch-icons' is set
190
+
191
+ if (params.has('apple-touch-icons')) {
192
+ params.set('as', 'metadata');
193
+ }
194
+
132
195
  // > Process presets
133
196
 
134
197
  if (presetName) {
@@ -73,56 +73,14 @@ declare module '*&preset=blur' {
73
73
 
74
74
  /* For favicons */
75
75
 
76
- // Classic browser tab icon
77
- declare module '*?w=16' {
76
+ // Generate all favicon sizes (16, 32, 48, 120, 152, 167, 180, 192, 512)
77
+ declare module '*?favicons' {
78
78
  const out: ImageSource;
79
79
  export default out;
80
80
  }
81
81
 
82
- // High-resolution browser support
83
- declare module '*?w=32' {
84
- const out: ImageSource;
85
- export default out;
86
- }
87
-
88
- // Windows desktop shortcuts
89
- declare module '*?w=48' {
90
- const out: ImageSource;
91
- export default out;
92
- }
93
-
94
- // iPhone older retina
95
- declare module '*?w=120' {
96
- const out: ImageSource;
97
- export default out;
98
- }
99
-
100
- // iPad retina, iOS Safari bookmarks
101
- declare module '*?w=152' {
102
- const out: ImageSource;
103
- export default out;
104
- }
105
-
106
- // iPad Pro
107
- declare module '*?w=167' {
108
- const out: ImageSource;
109
- export default out;
110
- }
111
-
112
- // iPhone retina, iOS home screen
113
- declare module '*?w=180' {
114
- const out: ImageSource;
115
- export default out;
116
- }
117
-
118
- // Android home screen, Chrome PWA
119
- declare module '*?w=192' {
120
- const out: ImageSource;
121
- export default out;
122
- }
123
-
124
- // PWA application icon, Android splash
125
- declare module '*?w=512' {
82
+ // Generate Apple touch icon sizes (120, 152, 167, 180)
83
+ declare module '*?apple-touch-icons' {
126
84
  const out: ImageSource;
127
85
  export default out;
128
86
  }
@@ -21,8 +21,7 @@
21
21
  * requestDevmode:function,
22
22
  * requestFullscreen:function,
23
23
  * gameWidth: number,
24
- * gameHeight: number,
25
- * iosLandscapeHeightQuirk: boolean
24
+ * gameHeight: number
26
25
  * }} SnippetParams
27
26
  */
28
27
 
@@ -134,34 +133,6 @@
134
133
  let isIos = $derived(os === 'iOS');
135
134
  let isAndroid = $derived(os === 'Android');
136
135
 
137
- /**
138
- * Detect iOS landscape height quirk (status bar appears/disappears)
139
- *
140
- * Detection: in landscape, innerHeight is ~20px less than screen.width
141
- *
142
- * NOTE: This detection is reactive and will update when dimensions change.
143
- * iOS PWA only fires viewport resize events when there's scrollable
144
- * overflow content, which we add via CSS ::after when quirk is detected.
145
- */
146
- let iosLandscapeHeightQuirk = $derived.by(() => {
147
- // Force reactivity by accessing these variables
148
- const currentLandscape = isLandscape;
149
- const currentIos = isIos;
150
- const width = iosWindowWidth ?? windowWidth;
151
- const height = iosWindowHeight ?? windowHeight;
152
-
153
- if (!currentLandscape || !currentIos) return false;
154
-
155
- if (!width || !height || !window.screen) return false;
156
-
157
- // In landscape: window.innerHeight should equal screen.width
158
- // If it's 20px less, the quirk is active
159
- const screenWidth = window.screen.width;
160
- const heightDifference = screenWidth - height;
161
-
162
- return heightDifference >= 18 && heightDifference <= 22;
163
- });
164
-
165
136
  // Debounce window dimensions on iOS to skip intermediate resize events
166
137
  let skipNextResize = false;
167
138
  let resetTimer;
@@ -343,29 +314,23 @@
343
314
  const html = document.documentElement;
344
315
  html.classList.add(gameBoxNoScroll);
345
316
 
346
- // Prevent page scroll while allowing child elements to scroll
347
- const preventPageScroll = () => {
348
- window.scrollTo(0, 0);
349
- };
317
+ // // Prevent page scroll while allowing child elements to scroll
318
+ // const preventPageScroll = () => {
319
+ // window.scrollTo(0, 0);
320
+ // };
350
321
 
351
- window.addEventListener('scroll', preventPageScroll, { passive: true });
322
+ // window.addEventListener('scroll', preventPageScroll, { passive: true });
352
323
 
324
+ // return () => {
325
+ // html.classList.remove(gameBoxNoScroll);
326
+ // window.removeEventListener('scroll', preventPageScroll);
327
+ // };
353
328
  return () => {
354
329
  html.classList.remove(gameBoxNoScroll);
355
- window.removeEventListener('scroll', preventPageScroll);
356
- };
357
- });
358
-
359
- // Toggle overflow content based on quirk detection
360
- $effect(() => {
361
- const html = document.documentElement;
362
- if (iosLandscapeHeightQuirk) {
363
- html.classList.add('ios-height-quirck');
364
- } else {
365
- html.classList.remove('ios-height-quirck');
366
330
  }
367
331
  });
368
332
 
333
+
369
334
  function getOS() {
370
335
  if (isAppleMobile) {
371
336
  return 'iOS';
@@ -520,8 +485,7 @@
520
485
  requestDevmode,
521
486
  requestFullscreen,
522
487
  gameWidth,
523
- gameHeight,
524
- iosLandscapeHeightQuirk
488
+ gameHeight
525
489
  })}
526
490
  </ScaledContainer>
527
491
  <!-- Portrait content -->
@@ -545,8 +509,7 @@
545
509
  requestDevmode,
546
510
  requestFullscreen,
547
511
  gameWidth,
548
- gameHeight,
549
- iosLandscapeHeightQuirk
512
+ gameHeight
550
513
  })}
551
514
  </ScaledContainer>
552
515
  {:else if supportsFullscreen && !isDevMode}
@@ -570,8 +533,7 @@
570
533
  requestDevmode,
571
534
  requestFullscreen,
572
535
  gameWidth,
573
- gameHeight,
574
- iosLandscapeHeightQuirk
536
+ gameHeight
575
537
  })}
576
538
  </ScaledContainer>
577
539
  {:else if isMobile && snippetInstallOnHomeScreen && !isDevMode}
@@ -595,8 +557,7 @@
595
557
  requestDevmode,
596
558
  requestFullscreen,
597
559
  gameWidth,
598
- gameHeight,
599
- iosLandscapeHeightQuirk
560
+ gameHeight
600
561
  })}
601
562
  </ScaledContainer>
602
563
  {:else}
@@ -621,8 +582,7 @@
621
582
  requestDevmode,
622
583
  requestFullscreen,
623
584
  gameWidth,
624
- gameHeight,
625
- iosLandscapeHeightQuirk
585
+ gameHeight
626
586
  })}
627
587
  </ScaledContainer>
628
588
  <!-- Portrait content -->
@@ -646,8 +606,7 @@
646
606
  requestDevmode,
647
607
  requestFullscreen,
648
608
  gameWidth,
649
- gameHeight,
650
- iosLandscapeHeightQuirk
609
+ gameHeight
651
610
  })}
652
611
  </ScaledContainer>
653
612
  {/if}
@@ -674,8 +633,7 @@
674
633
  requestDevmode,
675
634
  requestFullscreen,
676
635
  gameWidth,
677
- gameHeight,
678
- iosLandscapeHeightQuirk
636
+ gameHeight
679
637
  })}
680
638
  </ScaledContainer>
681
639
  <!-- Portrait content -->
@@ -699,8 +657,7 @@
699
657
  requestDevmode,
700
658
  requestFullscreen,
701
659
  gameWidth,
702
- gameHeight,
703
- iosLandscapeHeightQuirk
660
+ gameHeight
704
661
  })}
705
662
  </ScaledContainer>
706
663
  {/if}
@@ -720,16 +677,13 @@
720
677
  }
721
678
 
722
679
  :global(html.game-box-no-scroll) {
723
- overflow: hidden; /* allow resize events, prevent via JavaScript */
680
+ /* Prevent all scrolling - clip is stricter than hidden */
681
+ overflow: clip;
724
682
  scrollbar-width: none; /* Firefox */
725
683
  -ms-overflow-style: none; /* IE and Edge */
726
- }
727
- /* Only add overflow content when quirk is detected */
728
- :global(html.game-box-no-scroll.ios-height-quirck::after) {
729
- content: '\A'; /* newline character */
730
- white-space: pre; /* preserve newline */
731
- display: block;
732
- height: 20px; /* create overflow to trigger iOS resize events */
684
+ /* Prevent bounce/overscroll on iOS */
685
+ overscroll-behavior: none;
686
+ -webkit-overflow-scrolling: auto;
733
687
  }
734
688
  :global(html.game-box-no-scroll::-webkit-scrollbar) {
735
689
  display: none;
@@ -100,7 +100,6 @@ declare const GameBox: import("svelte").Component<{
100
100
  requestFullscreen: Function;
101
101
  gameWidth: number;
102
102
  gameHeight: number;
103
- iosLandscapeHeightQuirk: boolean;
104
103
  }]>;
105
104
  snippetPortrait?: import("svelte").Snippet<[{
106
105
  isLandscape: boolean;
@@ -115,7 +114,6 @@ declare const GameBox: import("svelte").Component<{
115
114
  requestFullscreen: Function;
116
115
  gameWidth: number;
117
116
  gameHeight: number;
118
- iosLandscapeHeightQuirk: boolean;
119
117
  }]>;
120
118
  snippetRequireFullscreen?: import("svelte").Snippet<[{
121
119
  isLandscape: boolean;
@@ -130,7 +128,6 @@ declare const GameBox: import("svelte").Component<{
130
128
  requestFullscreen: Function;
131
129
  gameWidth: number;
132
130
  gameHeight: number;
133
- iosLandscapeHeightQuirk: boolean;
134
131
  }]>;
135
132
  snippetInstallOnHomeScreen?: import("svelte").Snippet<[{
136
133
  isLandscape: boolean;
@@ -145,7 +142,6 @@ declare const GameBox: import("svelte").Component<{
145
142
  requestFullscreen: Function;
146
143
  gameWidth: number;
147
144
  gameHeight: number;
148
- iosLandscapeHeightQuirk: boolean;
149
145
  }]>;
150
146
  }, {}, "">;
151
147
  type SnippetParams = {
@@ -161,5 +157,4 @@ type SnippetParams = {
161
157
  requestFullscreen: Function;
162
158
  gameWidth: number;
163
159
  gameHeight: number;
164
- iosLandscapeHeightQuirk: boolean;
165
160
  };
@@ -248,6 +248,22 @@ The component includes special handling for mobile PWAs:
248
248
  - **Screen Orientation**: Listens for orientation changes and updates layout
249
249
  - **No Scroll**: Automatically prevents scrolling when active
250
250
 
251
+ ### iOS Landscape Height Bug
252
+
253
+ For games and fullscreen applications, use `viewport-fit=cover` in your
254
+ viewport meta tag to prevent iOS landscape height bugs:
255
+
256
+ ```html
257
+ <meta name="viewport"
258
+ content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no,
259
+ width=device-width, height=device-height, viewport-fit=cover" />
260
+ ```
261
+
262
+ This is handled automatically by the `PWA.svelte` component in the `(meta)`
263
+ folder when `disablePageZoom` is enabled. The `viewport-fit=cover` setting
264
+ ensures the viewport extends into safe areas on iOS devices, preventing a
265
+ common bug where landscape mode shows incorrect viewport heights.
266
+
251
267
  ## Development Mode
252
268
 
253
269
  The component automatically enables development mode when:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -1,577 +0,0 @@
1
- <script>
2
- import { onMount } from 'svelte';
3
-
4
- import {
5
- getGameWidthOnLandscape,
6
- getGameWidthOnPortrait
7
- } from './gamebox.util.js';
8
-
9
- import { enableContainerScaling } from '$lib/design/index.js';
10
- // import { enableContainerScaling } from '@hkdigital/lib-core/design/index.js';
11
-
12
- /**
13
- * @typedef {{
14
- * isMobile:boolean,
15
- * os:'Android'|'iOS',
16
- * isFullscreen:boolean,
17
- * isDevMode:boolean,
18
- * requestDevmode:function,
19
- * requestFullscreen:function,
20
- * gameWidth: number,
21
- * gameHeight: number
22
- * }} SnippetParams
23
- */
24
-
25
- /**
26
- * @typedef {import('svelte').Snippet<[SnippetParams]>} GameBoxSnippet
27
- */
28
-
29
- /**
30
- * @type {{
31
- * base?: string,
32
- * bg?: string,
33
- * classes?: string,
34
- * style?: string,
35
- * aspectOnLandscape?: number,
36
- * aspectOnPortrait?: number,
37
- * marginLeft?: number,
38
- * marginRight?: number,
39
- * marginTop?: number,
40
- * marginBottom?: number,
41
- * center?: boolean,
42
- * enableScaling?: boolean,
43
- * designLandscape?: {width: number, height: number},
44
- * designPortrait?: {width: number, height: number},
45
- * clamping?: {
46
- * ui: {min: number, max: number},
47
- * textBase: {min: number, max: number},
48
- * textHeading: {min: number, max: number},
49
- * textUi: {min: number, max: number}
50
- * },
51
- * snippetLandscape?:GameBoxSnippet,
52
- * snippetPortrait?: GameBoxSnippet,
53
- * snippetRequireFullscreen?: GameBoxSnippet,
54
- * snippetInstallOnHomeScreen?: GameBoxSnippet,
55
- * [attr: string]: any
56
- * }}
57
- */
58
- const {
59
- // > Style
60
- base = '',
61
- bg = '',
62
- classes = '',
63
- style = '',
64
-
65
- // > Functional properties
66
- aspectOnLandscape,
67
- aspectOnPortrait,
68
-
69
- marginLeft = 0,
70
- marginRight = 0,
71
-
72
- marginTop = 0,
73
- marginBottom = 0,
74
-
75
- center,
76
-
77
- // > Scaling options
78
- enableScaling = false,
79
- designLandscape = { width: 1920, height: 1080 },
80
- designPortrait = { width: 1920, height: 1080 },
81
- clamping = {
82
- ui: { min: 0.3, max: 2 },
83
- textBase: { min: 0.75, max: 1.5 },
84
- textHeading: { min: 0.75, max: 2.25 },
85
- textUi: { min: 0.5, max: 1.25 }
86
- },
87
-
88
- // > Snippets
89
- snippetLandscape,
90
- snippetPortrait,
91
- snippetRequireFullscreen,
92
- snippetInstallOnHomeScreen
93
- } = $props();
94
-
95
- // > Game dimensions and state
96
- let windowWidth = $state();
97
- let windowHeight = $state();
98
-
99
- let gameWidth = $state();
100
- let gameHeight = $state();
101
-
102
- let iosWindowWidth = $state();
103
- let iosWindowHeight = $state();
104
-
105
- function getIsLandscape() {
106
- if (isPwa && isAppleMobile) {
107
- return iosWindowWidth > iosWindowHeight;
108
- } else {
109
- return windowWidth > windowHeight;
110
- }
111
- }
112
-
113
- let isLandscape = $state();
114
-
115
- // $derived.by(getIsLandscape);
116
-
117
- $effect(() => {
118
- isLandscape = getIsLandscape();
119
- });
120
-
121
- // Game container reference
122
- let gameContainer = $state();
123
-
124
- // Update game dimensions based on window size and orientation
125
- $effect(() => {
126
- const width = iosWindowWidth ?? windowWidth;
127
- const height = iosWindowHeight ?? windowHeight;
128
-
129
- if (!width || !height) return;
130
-
131
- const availWidth = width - marginLeft - marginRight;
132
- const availHeight = height - marginTop - marginBottom;
133
-
134
- // console.debug('GameBox margins:', {
135
- // marginLeft,
136
- // marginRight,
137
- // marginTop,
138
- // marginBottom
139
- // });
140
-
141
- let gameAspect;
142
-
143
- if (availWidth > availHeight) {
144
- gameWidth = getGameWidthOnLandscape({
145
- windowWidth: availWidth,
146
- windowHeight: availHeight,
147
- aspectOnLandscape
148
- });
149
- gameAspect = aspectOnLandscape;
150
- } else {
151
- gameWidth = getGameWidthOnPortrait({
152
- windowWidth: availWidth,
153
- windowHeight: availHeight,
154
- aspectOnPortrait
155
- });
156
- gameAspect = aspectOnPortrait;
157
- }
158
-
159
- if (gameAspect) {
160
- gameHeight = gameWidth / gameAspect;
161
- } else {
162
- gameHeight = availHeight;
163
- }
164
- });
165
-
166
- // Set up scaling if enabled, with orientation awareness
167
- $effect(() => {
168
- if (!enableScaling || !gameContainer || !gameWidth || !gameHeight) {
169
- return () => {}; // No-op cleanup if scaling not enabled or required elements missing
170
- }
171
-
172
- // Select the appropriate design based on orientation
173
- const activeDesign = isLandscape ? designLandscape : designPortrait;
174
-
175
- // console.debug(
176
- // `GameBox scaling [${isLandscape ? 'landscape' : 'portrait'}]:`,
177
- // `game: ${gameWidth}x${gameHeight}`,
178
- // `design: ${activeDesign.width}x${activeDesign.height}`
179
- // );
180
-
181
- // Apply scaling with the current design based on orientation
182
- return enableContainerScaling({
183
- container: gameContainer,
184
- design: activeDesign,
185
- clamping,
186
- getDimensions: () => ({
187
- width: gameWidth,
188
- height: gameHeight
189
- })
190
- });
191
- });
192
-
193
- let show = $state(false);
194
-
195
- const isAppleMobile = /iPhone|iPod/.test(navigator.userAgent);
196
-
197
- let isPwa = $state(false);
198
-
199
- let os = $state();
200
-
201
- let isMobile = $state(false);
202
-
203
- let isDevMode = $state(false);
204
-
205
- // Check: always true for home app?
206
- let isFullscreen = $state(false);
207
-
208
- let supportsFullscreen = $state(false);
209
-
210
- onMount(() => {
211
- supportsFullscreen = document.fullscreenEnabled;
212
-
213
- isMobile = getIsMobile();
214
-
215
- os = getOS();
216
-
217
- // Run before show
218
- isFullscreen = !!document.fullscreenElement;
219
-
220
- isPwa = window.matchMedia(
221
- '(display-mode: fullscreen) or (display-mode: standalone)'
222
- ).matches;
223
-
224
- isLandscape = getIsLandscape();
225
-
226
- show = true;
227
-
228
- function updateIosWidthHeight() {
229
- // const isPwa = window.matchMedia(
230
- // '(display-mode: fullscreen) or (display-mode: standalone)'
231
- // ).matches;
232
-
233
- if (isPwa && isAppleMobile) {
234
- const angle = screen.orientation.angle;
235
-
236
- if (angle === 90 || angle === 270) {
237
- iosWindowWidth = screen.height;
238
- iosWindowHeight = screen.width;
239
- } else {
240
- iosWindowWidth = screen.width;
241
- iosWindowHeight = screen.height;
242
- }
243
- // console.debug( { iosWindowWidth, iosWindowHeight } );
244
- }
245
- }
246
-
247
- updateIosWidthHeight();
248
-
249
- function updateOrientation(event) {
250
- // console.debug('updateOrientation');
251
- const type = event.target.type;
252
- const angle = event.target.angle;
253
-
254
- // isPwa = window.matchMedia(
255
- // '(display-mode: fullscreen) or (display-mode: standalone)'
256
- // ).matches;
257
-
258
- updateIosWidthHeight();
259
-
260
- console.debug(
261
- `ScreenOrientation change: ${type}, ${angle} degrees.`,
262
- isPwa,
263
- windowWidth,
264
- windowHeight,
265
- screen.width,
266
- screen.height,
267
- iosWindowWidth,
268
- iosWindowHeight
269
- );
270
-
271
- // if( angle
272
- }
273
-
274
- $effect(() => {
275
- screen.orientation.addEventListener('change', updateOrientation);
276
-
277
- return () => {
278
- screen.orientation.removeEventListener('change', updateOrientation);
279
- };
280
- });
281
-
282
- //
283
- });
284
-
285
- onMount(() => {
286
- const gameBoxNoScroll = 'game-box-no-scroll';
287
- const html = document.documentElement;
288
- html.classList.add(gameBoxNoScroll);
289
-
290
- return () => {
291
- html.classList.remove(gameBoxNoScroll);
292
- };
293
- });
294
-
295
- function getOS() {
296
- if (isAppleMobile) {
297
- return 'iOS';
298
- } else if (/Android/.test(navigator.userAgent)) {
299
- return 'Android';
300
- }
301
- }
302
-
303
- /**
304
- * Returns true if a device is a mobile phone (or similar)
305
- */
306
- function getIsMobile() {
307
- // @ts-ignore
308
- if (navigator?.userAgentData?.mobile !== undefined) {
309
- // Supports for mobile flag
310
- // @ts-ignore
311
- return navigator.userAgentData.mobile;
312
- } else if (isAppleMobile) {
313
- return true;
314
- } else if (/Android/.test(navigator.userAgent)) {
315
- return true;
316
- }
317
-
318
- return false;
319
- }
320
-
321
- /**
322
- * Returns true if the window is in full screen
323
- * - Checks if CSS thinks we're in fullscreen mode
324
- * - Checks if there is a fullscreen element (for safari)
325
- */
326
- function getIsFullscreen() {
327
- if (
328
- window.matchMedia(
329
- '(display-mode: fullscreen) or (display-mode: standalone)'
330
- ).matches
331
- ) {
332
- return true;
333
- } else if (document.fullscreenElement) {
334
- // Safari
335
- return true;
336
- }
337
-
338
- return false;
339
- }
340
-
341
- async function requestFullscreen() {
342
- console.debug('Request full screen');
343
- show = false;
344
-
345
- await document.documentElement.requestFullscreen();
346
- isFullscreen = true;
347
-
348
- setTimeout(() => {
349
- show = true;
350
- }, 1000);
351
- }
352
-
353
- // async function exitFullscreen() {
354
- // console.debug('Exit full screen');
355
- // show = false;
356
-
357
- // await document.exitFullscreen();
358
- // isFullscreen = false;
359
-
360
- // setTimeout( () => { show = true; }, 1000 );
361
- // }
362
-
363
- $effect(() => {
364
- // Update isFullscreen if window width or height changes
365
-
366
- windowWidth;
367
- windowHeight;
368
-
369
- isFullscreen = getIsFullscreen();
370
-
371
- // if( !isFullscreen )
372
- // {
373
- // show = false;
374
- // setTimeout( () => { show = true; }, 1000 );
375
- // }
376
-
377
- // console.debug('isFullscreen', isFullscreen);
378
- });
379
-
380
- isDevMode = false;
381
-
382
- function requestDevmode() {
383
- isDevMode = true;
384
- console.debug(isDevMode);
385
- }
386
-
387
- $effect(() => {
388
- if (location.hostname === 'localhost') {
389
- isDevMode = true;
390
- }
391
- });
392
-
393
- $effect(() => {
394
- if (isFullscreen) {
395
- const url = new URL(window.location.href);
396
- url.searchParams.set('preset', 'cinema');
397
- window.history.pushState({}, '', url);
398
- }
399
- });
400
- </script>
401
-
402
- <svelte:window bind:innerWidth={windowWidth} bind:innerHeight={windowHeight} />
403
-
404
- {#if gameHeight}
405
- <div class:center>
406
- <div
407
- data-component="game-box"
408
- data-orientation={isLandscape ? 'landscape' : 'portrait'}
409
- bind:this={gameContainer}
410
- class="{base} {bg} {classes}"
411
- class:isMobile
412
- style:width="{gameWidth}px"
413
- style:height="{gameHeight}px"
414
- style:--game-width={gameWidth}
415
- style:--game-height={gameHeight}
416
- style:margin-left="{marginLeft}px"
417
- style:margin-right="{marginRight}px"
418
- style:margin-top="{marginTop}px"
419
- style:margin-bottom="{marginBottom}px"
420
- {style}
421
- >
422
- {#if show}
423
- {#if isLandscape}
424
- <!-- Landscape -->
425
- {#if snippetRequireFullscreen}
426
- <!-- Require fullscreen -->
427
- {#if isFullscreen && !isDevMode}
428
- {@render snippetLandscape({
429
- isMobile,
430
- os,
431
- isFullscreen,
432
- isDevMode,
433
- requestDevmode,
434
- requestFullscreen,
435
- gameWidth,
436
- gameHeight
437
- })}
438
- {:else if supportsFullscreen && !isDevMode}
439
- <!-- Require fullscreen (on landscape) -->
440
- {@render snippetRequireFullscreen({
441
- isMobile,
442
- os,
443
- isFullscreen,
444
- isDevMode,
445
- requestDevmode,
446
- requestFullscreen,
447
- gameWidth,
448
- gameHeight
449
- })}
450
- {:else if isMobile && snippetInstallOnHomeScreen && !isDevMode}
451
- <!-- Require install on home screen on mobile -->
452
- {@render snippetInstallOnHomeScreen({
453
- isMobile,
454
- os,
455
- isFullscreen,
456
- isDevMode,
457
- requestDevmode,
458
- requestFullscreen,
459
- gameWidth,
460
- gameHeight
461
- })}
462
- {:else}
463
- {@render snippetLandscape({
464
- isMobile,
465
- os,
466
- isFullscreen,
467
- isDevMode,
468
- requestDevmode,
469
- requestFullscreen,
470
- gameWidth,
471
- gameHeight
472
- })}
473
- {/if}
474
- {:else}
475
- <!-- Do not require fullscreen -->
476
- <!-- *we do not try install home app -->
477
- {@render snippetLandscape({
478
- isMobile,
479
- os,
480
- isFullscreen,
481
- isDevMode,
482
- requestDevmode,
483
- requestFullscreen,
484
- gameWidth,
485
- gameHeight
486
- })}
487
- {/if}
488
- {:else}
489
- <!-- Portrait -->
490
- {#if snippetRequireFullscreen}
491
- <!-- Require fullscreen -->
492
- {#if isFullscreen && !isDevMode}
493
- {@render snippetPortrait({
494
- isMobile,
495
- os,
496
- isFullscreen,
497
- isDevMode,
498
- requestDevmode,
499
- requestFullscreen,
500
- gameWidth,
501
- gameHeight
502
- })}
503
- {:else if supportsFullscreen && !isDevMode}
504
- <!-- Require fullscreen (on landscape) -->
505
- {@render snippetRequireFullscreen({
506
- isMobile,
507
- os,
508
- isFullscreen,
509
- isDevMode,
510
- requestDevmode,
511
- requestFullscreen,
512
- gameWidth,
513
- gameHeight
514
- })}
515
- {:else if isMobile && snippetInstallOnHomeScreen && !isDevMode}
516
- <!-- Require install on home screen on mobile -->
517
- {@render snippetInstallOnHomeScreen({
518
- isMobile,
519
- os,
520
- isFullscreen,
521
- isDevMode,
522
- requestDevmode,
523
- requestFullscreen,
524
- gameWidth,
525
- gameHeight
526
- })}
527
- {:else}
528
- {@render snippetPortrait({
529
- isMobile,
530
- os,
531
- isFullscreen,
532
- isDevMode,
533
- requestDevmode,
534
- requestFullscreen,
535
- gameWidth,
536
- gameHeight
537
- })}
538
- {/if}
539
- {:else}
540
- <!-- Do not require fullscreen -->
541
- <!-- *we do not try install home app -->
542
- {@render snippetPortrait({
543
- isMobile,
544
- os,
545
- isFullscreen,
546
- isDevMode,
547
- requestDevmode,
548
- requestFullscreen,
549
- gameWidth,
550
- gameHeight
551
- })}
552
- {/if}
553
- {/if}
554
- {/if}
555
- </div>
556
- </div>
557
- {/if}
558
-
559
- <style>
560
- .center {
561
- display: grid;
562
- height: 100lvh;
563
- display: grid;
564
- justify-items: center;
565
- align-items: center;
566
- /* border: solid 1px red;*/
567
- }
568
-
569
- :global(html.game-box-no-scroll) {
570
- overflow: clip;
571
- scrollbar-width: none; /* Firefox */
572
- -ms-overflow-style: none; /* IE and Edge */
573
- }
574
- :global(html.game-box-no-scroll::-webkit-scrollbar) {
575
- display: none;
576
- }
577
- </style>