@hkdigital/lib-core 0.5.92 → 0.5.94

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 (43) hide show
  1. package/README.md +63 -9
  2. package/dist/browser/info/device.js +9 -7
  3. package/dist/config/generators/imagetools.d.ts +14 -0
  4. package/dist/config/generators/imagetools.js +55 -0
  5. package/dist/config/imagetools.d.ts +12 -0
  6. package/dist/logging/README.md +15 -53
  7. package/dist/meta/README.md +92 -0
  8. package/dist/meta/components/Favicons.svelte +30 -0
  9. package/dist/meta/components/Favicons.svelte.d.ts +103 -0
  10. package/dist/meta/components/PWA.svelte +51 -0
  11. package/dist/meta/components/PWA.svelte.d.ts +103 -0
  12. package/dist/meta/components/SEO.svelte +146 -0
  13. package/dist/meta/components/SEO.svelte.d.ts +108 -0
  14. package/dist/meta/components.d.ts +3 -0
  15. package/dist/meta/components.js +3 -0
  16. package/dist/meta/config.typedef.d.ts +98 -0
  17. package/dist/meta/config.typedef.js +44 -0
  18. package/dist/meta/typedef.d.ts +3 -0
  19. package/dist/meta/typedef.js +14 -0
  20. package/dist/meta/utils/lang.d.ts +29 -0
  21. package/dist/meta/utils/lang.js +84 -0
  22. package/dist/meta/utils/robots.d.ts +1 -0
  23. package/dist/meta/utils/robots.js +1 -0
  24. package/dist/meta/utils/sitemap.d.ts +1 -0
  25. package/dist/meta/utils/sitemap.js +1 -0
  26. package/dist/meta/utils.d.ts +3 -0
  27. package/dist/meta/utils.js +11 -0
  28. package/dist/services/PATTERNS.md +476 -0
  29. package/dist/services/PLUGINS.md +520 -0
  30. package/dist/services/README.md +156 -229
  31. package/package.json +1 -1
  32. package/dist/meta/robots.d.ts +0 -1
  33. package/dist/meta/robots.js +0 -5
  34. package/dist/meta/sitemap.d.ts +0 -1
  35. package/dist/meta/sitemap.js +0 -5
  36. /package/dist/meta/{robots/index.d.ts → utils/robots/robots.d.ts} +0 -0
  37. /package/dist/meta/{robots/index.js → utils/robots/robots.js} +0 -0
  38. /package/dist/meta/{robots → utils/robots}/typedef.d.ts +0 -0
  39. /package/dist/meta/{robots → utils/robots}/typedef.js +0 -0
  40. /package/dist/meta/{sitemap/index.d.ts → utils/sitemap/sitemap.d.ts} +0 -0
  41. /package/dist/meta/{sitemap/index.js → utils/sitemap/sitemap.js} +0 -0
  42. /package/dist/meta/{sitemap → utils/sitemap}/typedef.d.ts +0 -0
  43. /package/dist/meta/{sitemap → utils/sitemap}/typedef.js +0 -0
package/README.md CHANGED
@@ -3,7 +3,28 @@
3
3
  Core library that we use to power up our SvelteKit projects
4
4
 
5
5
  This is a library for [SvelteKit](https://svelte.dev/) projects.
6
- It contains common code, base components and documentation that help you with setting up a new project.
6
+ It contains common code, base components and documentation that help
7
+ you with setting up a new project.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Using the library](#using-the-library)
12
+ - [Install](#install)
13
+ - [Peer Dependencies](#peer-dependencies)
14
+ - [Design System & Configuration](#design-system--configuration)
15
+ - [Logging System](#logging-system)
16
+ - [Documentation](#documentation)
17
+ - [Update](#update)
18
+ - [Available scripts](#available-scripts)
19
+ - [Import Validation](#import-validation)
20
+ - [Import Patterns and Export Structure](#import-patterns-and-export-structure)
21
+ - [CSS Architecture](#css-architecture-appcss)
22
+ - [Critical: data-theme Attribute](#critical-data-theme-attribute)
23
+ - [Building the library](#building-the-library)
24
+ - [Running the showcase app](#running-the-showcase-app)
25
+ - [Developing](#developing)
26
+ - [Publishing](#publishing)
27
+ - [Contribute](#contribute)
7
28
 
8
29
  ## Using the library
9
30
 
@@ -247,14 +268,47 @@ The library includes a comprehensive logging system that provides:
247
268
 
248
269
  ## Documentation
249
270
 
250
- For detailed setup guides and configuration:
251
- - **Project setup**: [docs/setup/new-project.md](./docs/setup/new-project.md) - SvelteKit project setup
252
- - **Library setup**: [docs/setup/new-lib.md](./docs/setup/new-lib.md) - SvelteKit library setup
253
- - **Services & logging**: [docs/setup/services-logging.md](./docs/setup/services-logging.md) - Service management architecture
254
- - **Configuration files**: [docs/config/root-config-files.md](./docs/config/root-config-files.md) - Config file reference
255
- - **Design system**: [src/lib/design/README.md](./src/lib/design/README.md) - Design tokens and theming
256
- - **Vite configuration**: [src/lib/config/README.md](./src/lib/config/README.md) - Build configuration
257
- - **Logging system**: [src/lib/logging/README.md](./src/lib/logging/README.md) - Server and client logging
271
+ Comprehensive documentation organized by topic.
272
+
273
+ ### Getting Started
274
+
275
+ Start here if you're setting up a new project or library:
276
+
277
+ - **[New project setup](./docs/setup/new-project.md)** - Complete guide
278
+ for setting up a SvelteKit application with lib-core
279
+ - **[New library setup](./docs/setup/new-lib.md)** - Complete guide for
280
+ setting up a SvelteKit library with lib-core
281
+
282
+ ### Architecture Guides
283
+
284
+ Learn how the different systems work together:
285
+
286
+ - **[Services & logging architecture](./docs/setup/services-logging.md)**
287
+ - How service management and logging integrate with SvelteKit
288
+ - **[Service patterns](./src/lib/services/PATTERNS.md)** - Best
289
+ practices and design patterns for implementing services
290
+ - **[Service plugins](./src/lib/services/PLUGINS.md)** - ConfigPlugin
291
+ and custom plugin development
292
+
293
+ ### API Reference
294
+
295
+ Detailed API documentation for each module:
296
+
297
+ - **[Logging](./src/lib/logging/README.md)** - Server and client
298
+ logging API, log levels, and formatters
299
+ - **[Services](./src/lib/services/README.md)** - ServiceBase and
300
+ ServiceManager API reference
301
+ - **[Design system](./src/lib/design/README.md)** - Design tokens,
302
+ theming, and UI utilities
303
+ - **[Vite configuration](./src/lib/config/README.md)** - Build
304
+ configuration and optimization
305
+
306
+ ### Configuration
307
+
308
+ Reference documentation for configuration files:
309
+
310
+ - **[Root config files](./docs/config/root-config-files.md)** - Guide
311
+ to vite.config.js, tailwind.config.js, and other root configs
258
312
 
259
313
  ### Update
260
314
 
@@ -131,13 +131,8 @@ export function getIsPhone() {
131
131
  * @returns {boolean} true if mobile device
132
132
  */
133
133
  export function getIsMobile() {
134
- // @ts-ignore
135
- if (navigator?.userAgentData?.mobile !== undefined) {
136
- // Modern API - most reliable
137
- // @ts-ignore
138
- return navigator.userAgentData.mobile;
139
- }
140
-
134
+ // Check Apple devices first - userAgentData.mobile is unreliable for
135
+ // iPads with M-series chips (reports false despite being mobile)
141
136
  if (getIsAppleMobile()) {
142
137
  return true;
143
138
  }
@@ -146,6 +141,13 @@ export function getIsMobile() {
146
141
  return true;
147
142
  }
148
143
 
144
+ // @ts-ignore
145
+ if (navigator?.userAgentData?.mobile !== undefined) {
146
+ // Modern API - use as fallback
147
+ // @ts-ignore
148
+ return navigator.userAgentData.mobile;
149
+ }
150
+
149
151
  return false;
150
152
  }
151
153
 
@@ -8,6 +8,12 @@
8
8
  * @param {number[]} [options.faviconSizes=FAVICON_SIZES]
9
9
  * @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
10
10
  *
11
+ * @param {{width: number, height: number}} [options.seoLandscapeSize]
12
+ * SEO landscape image size
13
+ *
14
+ * @param {{width: number, height: number}} [options.seoSquareSize]
15
+ * SEO square image size
16
+ *
11
17
  * @returns {(
12
18
  * entries: [string, string[]][]
13
19
  * ) => (Record<string, string | string[]>[])}
@@ -17,6 +23,14 @@ export function generateResponseConfigs(options?: {
17
23
  thumbnailWidth?: number | undefined;
18
24
  faviconSizes?: number[] | undefined;
19
25
  appleTouchSizes?: number[] | undefined;
26
+ seoLandscapeSize?: {
27
+ width: number;
28
+ height: number;
29
+ } | undefined;
30
+ seoSquareSize?: {
31
+ width: number;
32
+ height: number;
33
+ } | undefined;
20
34
  }): (entries: [string, string[]][]) => (Record<string, string | string[]>[]);
21
35
  /**
22
36
  * Configures and returns a function that can be used as
@@ -21,6 +21,9 @@ const APPLE_TOUCH_SIZES = [
21
21
  180 // iPhone retina, iOS home screen
22
22
  ];
23
23
 
24
+ const SEO_LANDSCAPE_SIZE = { width: 1200, height: 630 };
25
+ const SEO_SQUARE_SIZE = { width: 1200, height: 1200 };
26
+
24
27
  const DEFAULT_PRESETS = {
25
28
  default: {
26
29
  format: 'avif',
@@ -70,6 +73,12 @@ const DEFAULT_PRESETS = {
70
73
  * @param {number[]} [options.faviconSizes=FAVICON_SIZES]
71
74
  * @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
72
75
  *
76
+ * @param {{width: number, height: number}} [options.seoLandscapeSize]
77
+ * SEO landscape image size
78
+ *
79
+ * @param {{width: number, height: number}} [options.seoSquareSize]
80
+ * SEO square image size
81
+ *
73
82
  * @returns {(
74
83
  * entries: [string, string[]][]
75
84
  * ) => (Record<string, string | string[]>[])}
@@ -115,15 +124,25 @@ export function generateResponseConfigs(options) {
115
124
 
116
125
  // @ts-ignore
117
126
  const isAppleTouchIcon = !!entries.find(([key]) => key === 'apple-touch-icons');
127
+
128
+ // @ts-ignore
129
+ const isSeoLandscape = !!entries.find(([key]) => key === 'seo-landscape');
130
+
131
+ // @ts-ignore
132
+ const isSeoSquare = !!entries.find(([key]) => key === 'seo-square');
118
133
  // console.log('responsiveConfig found:', !!responsiveConfig);
119
134
 
120
135
  const widths = options?.widths ?? DEFAULT_WIDTHS;
121
136
  const faviconSizes = options?.faviconSizes ?? FAVICON_SIZES;
122
137
  const appleTouchSizes = options?.appleTouchSizes ?? APPLE_TOUCH_SIZES;
138
+ const seoLandscapeSize = options?.seoLandscapeSize ?? SEO_LANDSCAPE_SIZE;
139
+ const seoSquareSize = options?.seoSquareSize ?? SEO_SQUARE_SIZE;
123
140
 
124
141
  delete configPairs.responsive;
125
142
  delete configPairs.favicons;
126
143
  delete configPairs['apple-touch-icons'];
144
+ delete configPairs['seo-landscape'];
145
+ delete configPairs['seo-square'];
127
146
 
128
147
  // Always include the main image(s) and a thumbnail version
129
148
  const thumbnailConfig = {
@@ -163,6 +182,30 @@ export function generateResponseConfigs(options) {
163
182
  return appleTouchConfigs;
164
183
  }
165
184
 
185
+ // Handle seo-landscape directive - generate landscape SEO image
186
+
187
+ if (isSeoLandscape) {
188
+ return [{
189
+ ...configPairs,
190
+ w: String(seoLandscapeSize.width),
191
+ h: String(seoLandscapeSize.height),
192
+ format: 'jpg',
193
+ quality: '95'
194
+ }];
195
+ }
196
+
197
+ // Handle seo-square directive - generate square SEO image
198
+
199
+ if (isSeoSquare) {
200
+ return [{
201
+ ...configPairs,
202
+ w: String(seoSquareSize.width),
203
+ h: String(seoSquareSize.height),
204
+ format: 'jpg',
205
+ quality: '95'
206
+ }];
207
+ }
208
+
166
209
  if (!responsiveConfig) {
167
210
  // Directive 'responsive' was not set => return original + thumbnail
168
211
  const originalConfig = configPairs; // No 'w' means original dimensions
@@ -227,6 +270,18 @@ export function generateDefaultDirectives(options) {
227
270
  params.set('as', 'metadata');
228
271
  }
229
272
 
273
+ // > Return picture (URL) if directive 'seo-landscape' is set
274
+
275
+ if (params.has('seo-landscape')) {
276
+ params.set('as', 'picture');
277
+ }
278
+
279
+ // > Return picture (URL) if directive 'seo-square' is set
280
+
281
+ if (params.has('seo-square')) {
282
+ params.set('as', 'picture');
283
+ }
284
+
230
285
  // > Process presets
231
286
 
232
287
  if (presetName) {
@@ -84,3 +84,15 @@ declare module '*?apple-touch-icons' {
84
84
  const out: ImageSource;
85
85
  export default out;
86
86
  }
87
+
88
+ // Generate SEO landscape image (1200x630)
89
+ declare module '*?seo-landscape' {
90
+ const out: string;
91
+ export default out;
92
+ }
93
+
94
+ // Generate SEO square image (1200x1200)
95
+ declare module '*?seo-square' {
96
+ const out: string;
97
+ export default out;
98
+ }
@@ -3,6 +3,14 @@
3
3
  Universal logging utilities for SvelteKit applications with
4
4
  server/client/universal logger factories.
5
5
 
6
+ **See also:**
7
+ - **Architecture**: [docs/setup/services-logging.md](../../docs/setup/services-logging.md)
8
+ - How logging and services work together
9
+ - **Services**: [src/lib/services/README.md](../services/README.md) -
10
+ Service management system
11
+ - **Main README**: [README.md](../../README.md) - Library overview and
12
+ setup
13
+
6
14
  ## Installation
7
15
 
8
16
  ```bash
@@ -182,61 +190,15 @@ export function handleError({ error, event }) {
182
190
  }
183
191
  ```
184
192
 
185
- ### Client Service Integration
186
-
187
- When integrating with a service management system, you can set up global
188
- error handling and forward service logs to the main logger:
189
-
190
- ```javascript
191
- import { ServiceManager } from '@hkdigital/lib-core/services/index.js';
192
- import { initClientLogger } from '$lib/logging/client.js';
193
-
194
- /** @type {ServiceManager} */
195
- let manager;
196
-
197
- export async function initClientServices() {
198
- if (!manager) {
199
- const logger = initClientLogger();
200
-
201
- // Catch errors and unhandled promise rejections
202
-
203
- // Log unhandled errors
204
- window.addEventListener('error', (event) => {
205
- logger.error(event, { url: window.location.pathname });
206
- event.preventDefault();
207
- });
208
-
209
- // Log unhandled promise rejections
210
- window.addEventListener('unhandledrejection', (event) => {
211
- logger.error(event, { url: window.location.pathname });
212
- // Ignored by Firefox
213
- event.preventDefault();
214
- });
215
-
216
- manager = new ServiceManager({ debug: true });
217
-
218
- // Listen to all log events and forward them to the logger
219
- manager.onLogEvent((logEvent) => {
220
- logger.logFromEvent(logEvent);
221
- });
222
-
223
- // Register services
224
- manager.register(SERVICE_AUDIO, AudioService);
225
- manager.register(SERVICE_EVENT_LOG, EventLogService);
226
- manager.register(SERVICE_PLAYER_DATA, PlayerDataService);
227
- }
193
+ ### Service Integration
228
194
 
229
- await manager.startAll();
230
- return manager;
231
- }
195
+ For integrating logging with the service management system, including
196
+ how to forward service logs to the main logger, see:
232
197
 
233
- export function getManager() {
234
- if (!manager) {
235
- throw new Error('Client services should be initialised first');
236
- }
237
- return manager;
238
- }
239
- ```
198
+ - [Services README](../services/README.md) - ServiceManager log event
199
+ forwarding
200
+ - [Services & Logging Architecture](../../docs/setup/services-logging.md)
201
+ - Complete integration examples
240
202
 
241
203
  ## Development
242
204
 
@@ -62,6 +62,12 @@ export const GET = async ({ url }) => {
62
62
  * Supports wildcards (e.g., '*.example.com')
63
63
  * @property {string[]} [disallowedPaths]
64
64
  * Paths to block from indexing (e.g., '/admin', '/api/*')
65
+ * @property {boolean} [allowAiTraining=true]
66
+ * Allow AI training bots to crawl content for model training.
67
+ * Set to false to block bots like GPTBot, Google-Extended, CCBot, etc.
68
+ * @property {boolean} [allowAiReading=true]
69
+ * Allow AI assistants/chatbots to read content for user responses.
70
+ * Set to false to block bots like ChatGPT-User, Claude-Web, etc.
65
71
  */
66
72
  ```
67
73
 
@@ -108,6 +114,54 @@ const config = {
108
114
  // Sitemap: https://example.com/sitemap.xml
109
115
  ```
110
116
 
117
+ **AI bot control:**
118
+
119
+ ```javascript
120
+ // Block all AI bots from training on your content
121
+ const config = {
122
+ allowedHosts: '*',
123
+ allowAiTraining: false
124
+ };
125
+
126
+ // Generates additional blocks:
127
+ // User-agent: GPTBot
128
+ // Disallow: /
129
+ //
130
+ // User-agent: Google-Extended
131
+ // Disallow: /
132
+ // ... (and other AI training bots)
133
+ ```
134
+
135
+ **AI training vs AI reading:**
136
+
137
+ ```javascript
138
+ // Block training but allow reading (ChatGPT can browse, but not train)
139
+ const config = {
140
+ allowAiTraining: false, // Blocks GPTBot, Google-Extended, CCBot
141
+ allowAiReading: true // Allows ChatGPT-User, Claude-Web
142
+ };
143
+
144
+ // Block both training and reading
145
+ const config = {
146
+ allowAiTraining: false,
147
+ allowAiReading: false
148
+ };
149
+ ```
150
+
151
+ **Blocked AI training bots:**
152
+ - `GPTBot` - OpenAI training
153
+ - `Google-Extended` - Google AI training
154
+ - `CCBot` - Common Crawl
155
+ - `anthropic-ai` - Anthropic training
156
+ - `FacebookBot` - Meta AI training
157
+ - `PerplexityBot` - Perplexity AI training
158
+ - And others (see `src/lib/meta/robots/index.js` for full list)
159
+
160
+ **Blocked AI reading bots:**
161
+ - `ChatGPT-User` - ChatGPT browsing
162
+ - `Claude-Web` - Claude browsing
163
+ - `cohere-ai` - Cohere browsing
164
+
111
165
  **Sitemap reference:**
112
166
 
113
167
  Sitemap reference is always included for allowed hosts:
@@ -277,6 +331,44 @@ const robotsConfig = {
277
331
  // All hosts → Allow + Sitemap
278
332
  ```
279
333
 
334
+ ### Block AI training but allow search engines
335
+
336
+ ```javascript
337
+ // Protect premium content from AI training while allowing SEO
338
+ const robotsConfig = {
339
+ allowedHosts: ['mysite.com', 'www.mysite.com'],
340
+ disallowedPaths: ['/admin'],
341
+ allowAiTraining: false, // Block GPTBot, Google-Extended, etc.
342
+ allowAiReading: true // Allow ChatGPT browsing (user queries)
343
+ };
344
+
345
+ // Generates:
346
+ // User-agent: *
347
+ // Allow: /
348
+ // Disallow: /admin
349
+ // Sitemap: https://mysite.com/sitemap.xml
350
+ //
351
+ // User-agent: GPTBot
352
+ // Disallow: /
353
+ //
354
+ // User-agent: Google-Extended
355
+ // Disallow: /
356
+ // ... (other AI training bots)
357
+ ```
358
+
359
+ ### Block all AI bots completely
360
+
361
+ ```javascript
362
+ // Block both AI training and AI reading/browsing
363
+ const robotsConfig = {
364
+ allowedHosts: ['mysite.com'],
365
+ allowAiTraining: false,
366
+ allowAiReading: false
367
+ };
368
+
369
+ // Blocks: GPTBot, Google-Extended, CCBot, ChatGPT-User, Claude-Web, etc.
370
+ ```
371
+
280
372
  ### Complex sitemap configuration
281
373
 
282
374
  ```javascript
@@ -0,0 +1,30 @@
1
+ <script>
2
+ /**
3
+ * Favicons component
4
+ *
5
+ * Generates favicon and apple-touch-icon links for browsers and PWAs.
6
+ *
7
+ * @typedef {import('../typedef.js').MetaConfig} MetaConfig
8
+ */
9
+
10
+ /** @type {{ config: MetaConfig }} */
11
+ let { config } = $props();
12
+
13
+ const { faviconImages, appleTouchIcons } = config;
14
+ </script>
15
+
16
+ <svelte:head>
17
+ <!-- Basic favicon configuration -->
18
+ {#each faviconImages as img}
19
+ <link
20
+ rel="icon"
21
+ type="image/png"
22
+ sizes="{img.width}x{img.width}"
23
+ href={img.src}
24
+ />
25
+ {/each}
26
+
27
+ {#each appleTouchIcons as img}
28
+ <link rel="apple-touch-icon" sizes="{img.width}x{img.width}" href={img.src} />
29
+ {/each}
30
+ </svelte:head>
@@ -0,0 +1,103 @@
1
+ export default Favicons;
2
+ type Favicons = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const Favicons: import("svelte").Component<{
7
+ config: import("../config.typedef.js").MetaConfig;
8
+ }, {}, "">;
9
+ type $$ComponentProps = {
10
+ config: {
11
+ /**
12
+ * - Full app name
13
+ */
14
+ name: string;
15
+ /**
16
+ * - Short app name (max 12 characters)
17
+ */
18
+ shortName: string;
19
+ /**
20
+ * - App description for search engines
21
+ *
22
+ * Language and locale
23
+ */
24
+ description: string;
25
+ /**
26
+ * Language configurations
27
+ */
28
+ languages: Record<string, {
29
+ lang: string;
30
+ locale: string;
31
+ }>;
32
+ /**
33
+ * - Default language code
34
+ */
35
+ defaultLanguage: string;
36
+ /**
37
+ * - Default locale
38
+ *
39
+ * PWA theme and colors
40
+ */
41
+ defaultLocale: string;
42
+ /**
43
+ * - Theme color
44
+ */
45
+ backgroundAndThemeColor: string;
46
+ /**
47
+ * - Theme color for browser UI
48
+ */
49
+ themeColor: string;
50
+ /**
51
+ * - Background color
52
+ */
53
+ backgroundColor: string;
54
+ /**
55
+ * - iOS status bar style
56
+ */
57
+ statusBarStyle: string;
58
+ /**
59
+ * - Screen orientation
60
+ */
61
+ orientation: string;
62
+ /**
63
+ * - Disable pinch-to-zoom
64
+ *
65
+ * SEO images
66
+ */
67
+ disablePageZoom: boolean;
68
+ /**
69
+ * - Landscape SEO image URL (1200×630)
70
+ */
71
+ SeoImageLandscape?: string | undefined;
72
+ /**
73
+ * - Square SEO image URL (1200×1200)
74
+ *
75
+ * Favicon images (processed by imagetools)
76
+ */
77
+ SeoImageSquare?: string | undefined;
78
+ /**
79
+ * Processed favicon images
80
+ */
81
+ faviconImages: Array<{
82
+ src: string;
83
+ width: number;
84
+ }>;
85
+ /**
86
+ * Processed apple-touch-icon images
87
+ *
88
+ * Site configuration
89
+ */
90
+ appleTouchIcons: Array<{
91
+ src: string;
92
+ width: number;
93
+ }>;
94
+ /**
95
+ * Routes for sitemap.xml
96
+ */
97
+ siteRoutes: import("../typedef.js").SitemapRoute[];
98
+ /**
99
+ * Robots.txt configuration
100
+ */
101
+ robotsConfig: import("../typedef.js").RobotsConfig;
102
+ };
103
+ };
@@ -0,0 +1,51 @@
1
+ <script>
2
+ import { onMount } from 'svelte';
3
+
4
+ /**
5
+ * PWA component
6
+ *
7
+ * Generates Progressive Web App meta tags and viewport configuration.
8
+ *
9
+ * @typedef {import('../typedef.js').MetaConfig} MetaConfig
10
+ */
11
+
12
+ /** @type {{ config: MetaConfig }} */
13
+ let { config } = $props();
14
+
15
+ const { themeColor, statusBarStyle, name, shortName, disablePageZoom } =
16
+ config;
17
+
18
+ let shouldSetTitle = $state(false);
19
+
20
+ onMount(() => {
21
+ // Check if title element exists and has content
22
+ const titleElement = document.querySelector('title');
23
+ const hasTitle = titleElement && titleElement.textContent.trim() !== '';
24
+
25
+ if (!hasTitle) {
26
+ shouldSetTitle = true;
27
+ }
28
+ });
29
+ </script>
30
+
31
+ <svelte:head>
32
+ {#if shouldSetTitle}
33
+ <title>{name}</title>
34
+ {/if}
35
+
36
+ {#if !disablePageZoom}
37
+ <meta name="viewport" content="width=device-width, initial-scale=1">
38
+ {:else}
39
+ <meta name="viewport"
40
+ content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no, width=device-width, viewport-fit=cover" />
41
+ {/if}
42
+
43
+ <meta name="theme-color" content="{themeColor}">
44
+ <link rel="manifest" href="/manifest.json">
45
+
46
+ <meta name="mobile-web-app-capable" content="yes">
47
+
48
+ <!-- iOS-specific meta tags -->
49
+ <meta name="apple-mobile-web-app-status-bar-style" content="{statusBarStyle}">
50
+ <meta name="apple-mobile-web-app-title" content="{shortName}">
51
+ </svelte:head>