@hkdigital/lib-core 0.5.96 → 0.5.98
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/README.md +8 -56
- package/dist/config/generators/imagetools.d.ts +4 -10
- package/dist/config/generators/imagetools.js +43 -25
- package/dist/config/imagetools.d.ts +2 -2
- package/dist/meta/components/Favicons.svelte +1 -1
- package/dist/meta/components/Favicons.svelte.d.ts +9 -5
- package/dist/meta/components/PWA.svelte +2 -2
- package/dist/meta/components/PWA.svelte.d.ts +9 -5
- package/dist/meta/components/SEO.svelte +113 -77
- package/dist/meta/components/SEO.svelte.d.ts +28 -28
- package/dist/{config/meta.d.ts → meta/config.d.ts} +14 -14
- package/dist/meta/config.js +115 -0
- package/dist/meta/templates/README.md +3 -3
- package/dist/meta/templates/lib/{config/meta.d.ts → meta/config.d.ts} +22 -29
- package/dist/{config/meta.js → meta/templates/lib/meta/config.js} +10 -10
- package/dist/meta/templates/lib/meta/favicon.png +0 -0
- package/dist/meta/templates/lib/meta/preview-landscape.png +0 -0
- package/dist/meta/templates/lib/meta/preview-square.png +0 -0
- package/dist/meta/templates/routes/(meta)/manifest.json/+server.js +3 -4
- package/dist/meta/templates/routes/(meta)/robots.txt/+server.js +1 -1
- package/dist/meta/templates/routes/(meta)/sitemap.xml/+server.js +1 -1
- package/dist/meta/typedef.d.ts +100 -1
- package/dist/meta/typedef.js +45 -5
- package/dist/meta.d.ts +1 -1
- package/dist/meta.js +1 -1
- package/package.json +1 -1
- package/dist/meta/config.typedef.d.ts +0 -98
- package/dist/meta/config.typedef.js +0 -44
- package/dist/meta/templates/lib/config/meta.js +0 -112
- package/dist/meta/templates/lib/meta.d.ts +0 -8
- package/dist/meta/templates/lib/meta.js +0 -23
- /package/dist/meta/{templates/lib/assets/meta/favicon.png → favicon.png} +0 -0
- /package/dist/meta/{templates/lib/assets/meta/preview-landscape.png → preview-landscape.png} +0 -0
- /package/dist/meta/{templates/lib/assets/meta/preview-square.png → preview-square.png} +0 -0
package/README.md
CHANGED
|
@@ -204,72 +204,24 @@ This library includes a complete design system with Tailwind CSS integration. Ba
|
|
|
204
204
|
import heroResponsive from '$lib/assets/hero.jpg?preset=photo&responsive';
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
-
6. **Meta setup** -
|
|
207
|
+
6. **Meta setup (optional)** - SEO, PWA, and favicons:
|
|
208
208
|
|
|
209
209
|
The library includes templates for PWA configuration, SEO metadata,
|
|
210
210
|
favicon generation, manifest.json, sitemap.xml, and robots.txt.
|
|
211
211
|
|
|
212
|
-
**
|
|
212
|
+
**Quick setup:**
|
|
213
213
|
```bash
|
|
214
|
-
# Copy
|
|
214
|
+
# Copy template files to your project
|
|
215
215
|
cp -r node_modules/@hkdigital/lib-core/meta/templates/lib/* src/lib/
|
|
216
|
-
|
|
217
|
-
# Copy route endpoints to src/routes/
|
|
218
216
|
cp -r node_modules/@hkdigital/lib-core/meta/templates/routes/* src/routes/
|
|
219
217
|
```
|
|
220
218
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
- `favicon.png` (512×512px)
|
|
224
|
-
- `preview-landscape.png` (1200×630px for social media)
|
|
225
|
-
- `preview-square.png` (1200×1200px for social media)
|
|
226
|
-
- Edit `src/lib/config/meta.js`:
|
|
227
|
-
```javascript
|
|
228
|
-
export const name = 'Your App Name';
|
|
229
|
-
export const shortName = 'App';
|
|
230
|
-
export const description = 'Your app description';
|
|
231
|
-
export const backgroundAndThemeColor = '#082962';
|
|
232
|
-
|
|
233
|
-
// Add your site routes for sitemap
|
|
234
|
-
export const siteRoutes = ['/', '/about', '/contact'];
|
|
235
|
-
|
|
236
|
-
// Configure robots.txt
|
|
237
|
-
export const robotsConfig = {
|
|
238
|
-
allowedHosts: '*', // '*' allows all hosts
|
|
239
|
-
disallowedPaths: [],
|
|
240
|
-
allowAiTraining: true,
|
|
241
|
-
allowAiReading: true
|
|
242
|
-
};
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
**Integrate into root layout:**
|
|
246
|
-
```svelte
|
|
247
|
-
<!-- src/routes/+layout.svelte -->
|
|
248
|
-
<script>
|
|
249
|
-
import { Favicons, PWA, SEO, config } from '$lib/meta.js';
|
|
250
|
-
|
|
251
|
-
let { children, data } = $props();
|
|
252
|
-
</script>
|
|
253
|
-
|
|
254
|
-
<Favicons {config} />
|
|
255
|
-
<PWA {config} />
|
|
256
|
-
<SEO {config} locale={data?.locale} />
|
|
257
|
-
|
|
258
|
-
{@render children()}
|
|
259
|
-
```
|
|
219
|
+
Then customize `$lib/meta/config.js` and replace placeholder images
|
|
220
|
+
in `$lib/meta/` with your own.
|
|
260
221
|
|
|
261
|
-
**
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
- SEO meta tags and Open Graph support
|
|
265
|
-
- Social media preview images
|
|
266
|
-
- Multi-language support with auto-detection
|
|
267
|
-
- Dynamic `/manifest.json` endpoint
|
|
268
|
-
- Dynamic `/sitemap.xml` endpoint
|
|
269
|
-
- Dynamic `/robots.txt` with AI bot control
|
|
270
|
-
|
|
271
|
-
See the [meta template README](./src/lib/meta/templates/README.md) for
|
|
272
|
-
complete documentation.
|
|
222
|
+
**For complete setup instructions and documentation**, see:
|
|
223
|
+
- [Meta setup guide](./src/lib/meta/templates/README.md)
|
|
224
|
+
- [Meta utilities API](./src/lib/meta/README.md)
|
|
273
225
|
|
|
274
226
|
### Logging System
|
|
275
227
|
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* @param {number[]} [options.faviconSizes=FAVICON_SIZES]
|
|
9
9
|
* @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
|
|
10
10
|
*
|
|
11
|
-
* @param {
|
|
11
|
+
* @param {[number,number][]} [options.seoLandscapeSizes]
|
|
12
12
|
* SEO landscape image size
|
|
13
13
|
*
|
|
14
|
-
* @param {
|
|
14
|
+
* @param {[number,number][]} [options.seoSquareSizes]
|
|
15
15
|
* SEO square image size
|
|
16
16
|
*
|
|
17
17
|
* @returns {(
|
|
@@ -23,14 +23,8 @@ export function generateResponseConfigs(options?: {
|
|
|
23
23
|
thumbnailWidth?: number | undefined;
|
|
24
24
|
faviconSizes?: number[] | undefined;
|
|
25
25
|
appleTouchSizes?: number[] | undefined;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
height: number;
|
|
29
|
-
} | undefined;
|
|
30
|
-
seoSquareSize?: {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
} | undefined;
|
|
26
|
+
seoLandscapeSizes?: [number, number][] | undefined;
|
|
27
|
+
seoSquareSizes?: [number, number][] | undefined;
|
|
34
28
|
}): (entries: [string, string[]][]) => (Record<string, string | string[]>[]);
|
|
35
29
|
/**
|
|
36
30
|
* Configures and returns a function that can be used as
|
|
@@ -21,8 +21,15 @@ const APPLE_TOUCH_SIZES = [
|
|
|
21
21
|
180 // iPhone retina, iOS home screen
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
const SEO_LANDSCAPE_SIZES =
|
|
25
|
+
[
|
|
26
|
+
[1200, 630]
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const SEO_SQUARE_SIZES =
|
|
30
|
+
[
|
|
31
|
+
[1200,1200]
|
|
32
|
+
];
|
|
26
33
|
|
|
27
34
|
const DEFAULT_PRESETS = {
|
|
28
35
|
default: {
|
|
@@ -73,10 +80,10 @@ const DEFAULT_PRESETS = {
|
|
|
73
80
|
* @param {number[]} [options.faviconSizes=FAVICON_SIZES]
|
|
74
81
|
* @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
|
|
75
82
|
*
|
|
76
|
-
* @param {
|
|
83
|
+
* @param {[number,number][]} [options.seoLandscapeSizes]
|
|
77
84
|
* SEO landscape image size
|
|
78
85
|
*
|
|
79
|
-
* @param {
|
|
86
|
+
* @param {[number,number][]} [options.seoSquareSizes]
|
|
80
87
|
* SEO square image size
|
|
81
88
|
*
|
|
82
89
|
* @returns {(
|
|
@@ -135,8 +142,8 @@ export function generateResponseConfigs(options) {
|
|
|
135
142
|
const widths = options?.widths ?? DEFAULT_WIDTHS;
|
|
136
143
|
const faviconSizes = options?.faviconSizes ?? FAVICON_SIZES;
|
|
137
144
|
const appleTouchSizes = options?.appleTouchSizes ?? APPLE_TOUCH_SIZES;
|
|
138
|
-
const
|
|
139
|
-
const
|
|
145
|
+
const seoLandscapeSizes = options?.seoLandscapeSizes ?? SEO_LANDSCAPE_SIZES;
|
|
146
|
+
const seoSquareSizes = options?.seoSquareSizes ?? SEO_SQUARE_SIZES;
|
|
140
147
|
|
|
141
148
|
delete configPairs.responsive;
|
|
142
149
|
delete configPairs.favicons;
|
|
@@ -185,25 +192,35 @@ export function generateResponseConfigs(options) {
|
|
|
185
192
|
// Handle seo-landscape directive - generate landscape SEO image
|
|
186
193
|
|
|
187
194
|
if (isSeoLandscape) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
const seoLandscapeConfigs = seoLandscapeSizes.map(([w, h]) => {
|
|
196
|
+
return {
|
|
197
|
+
...configPairs,
|
|
198
|
+
w: String(w),
|
|
199
|
+
h: String(h),
|
|
200
|
+
format: 'jpg',
|
|
201
|
+
quality: '95'
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
// console.log('**** Returning seo-landscape configs:', seoLandscapeConfigs);
|
|
205
|
+
// @note return two elements, otherwise imagetools collapses the array
|
|
206
|
+
return [...seoLandscapeConfigs, thumbnailConfig];
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
// Handle seo-square directive - generate square SEO image
|
|
198
210
|
|
|
199
211
|
if (isSeoSquare) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
const seoSquareConfigs = seoSquareSizes.map(([w,h]) => {
|
|
213
|
+
return {
|
|
214
|
+
...configPairs,
|
|
215
|
+
w: String(w),
|
|
216
|
+
h: String(h),
|
|
217
|
+
format: 'jpg',
|
|
218
|
+
quality: '95'
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
// console.log('**** Returning seo-square configs:', seoSquareConfigs);
|
|
222
|
+
// @note return two elements, otherwise imagetools collapses the array
|
|
223
|
+
return [...seoSquareConfigs, thumbnailConfig];
|
|
207
224
|
}
|
|
208
225
|
|
|
209
226
|
if (!responsiveConfig) {
|
|
@@ -217,6 +234,7 @@ export function generateResponseConfigs(options) {
|
|
|
217
234
|
const responsiveConfigs = widths.map((w) => {
|
|
218
235
|
return { ...configPairs, w: String(w) };
|
|
219
236
|
});
|
|
237
|
+
|
|
220
238
|
const result = [...responsiveConfigs, thumbnailConfig];
|
|
221
239
|
// console.log('Returning responsive + thumbnail configs:', result);
|
|
222
240
|
return result;
|
|
@@ -247,7 +265,7 @@ export function generateDefaultDirectives(options) {
|
|
|
247
265
|
|
|
248
266
|
let presetName = params.get('preset');
|
|
249
267
|
|
|
250
|
-
// >
|
|
268
|
+
// > Responsive image support: return meta data instead of image data
|
|
251
269
|
|
|
252
270
|
// @see https://github.com/JonasKruckenberg/
|
|
253
271
|
// imagetools/blob/main/docs/directives.md#metadata
|
|
@@ -270,16 +288,16 @@ export function generateDefaultDirectives(options) {
|
|
|
270
288
|
params.set('as', 'metadata');
|
|
271
289
|
}
|
|
272
290
|
|
|
273
|
-
// > Return
|
|
291
|
+
// > Return metadata if directive 'seo-landscape' is set
|
|
274
292
|
|
|
275
293
|
if (params.has('seo-landscape')) {
|
|
276
|
-
params.set('as', '
|
|
294
|
+
params.set('as', 'metadata');
|
|
277
295
|
}
|
|
278
296
|
|
|
279
|
-
// > Return
|
|
297
|
+
// > Return metadata if directive 'seo-square' is set
|
|
280
298
|
|
|
281
299
|
if (params.has('seo-square')) {
|
|
282
|
-
params.set('as', '
|
|
300
|
+
params.set('as', 'metadata');
|
|
283
301
|
}
|
|
284
302
|
|
|
285
303
|
// > Process presets
|
|
@@ -87,12 +87,12 @@ declare module '*?apple-touch-icons' {
|
|
|
87
87
|
|
|
88
88
|
// Generate SEO landscape image (1200x630)
|
|
89
89
|
declare module '*?seo-landscape' {
|
|
90
|
-
const out:
|
|
90
|
+
const out: ImageSource;
|
|
91
91
|
export default out;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// Generate SEO square image (1200x1200)
|
|
95
95
|
declare module '*?seo-square' {
|
|
96
|
-
const out:
|
|
96
|
+
const out: ImageSource;
|
|
97
97
|
export default out;
|
|
98
98
|
}
|
|
@@ -4,7 +4,7 @@ type Favicons = {
|
|
|
4
4
|
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
5
|
};
|
|
6
6
|
declare const Favicons: import("svelte").Component<{
|
|
7
|
-
config: import("../
|
|
7
|
+
config: import("../typedef.js").MetaConfig;
|
|
8
8
|
}, {}, "">;
|
|
9
9
|
type $$ComponentProps = {
|
|
10
10
|
config: {
|
|
@@ -66,15 +66,19 @@ type $$ComponentProps = {
|
|
|
66
66
|
*/
|
|
67
67
|
disablePageZoom: boolean;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Landscape SEO image URL (1200×630)
|
|
70
70
|
*/
|
|
71
|
-
|
|
71
|
+
previewImageLandscape?: import("../../config/typedef.js").ImageSource | undefined;
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* Square SEO image URL (1200×1200)
|
|
74
|
+
*/
|
|
75
|
+
previewImageSquare?: import("../../config/typedef.js").ImageSource | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* - Alt text for social media image
|
|
74
78
|
*
|
|
75
79
|
* Favicon images (processed by imagetools)
|
|
76
80
|
*/
|
|
77
|
-
|
|
81
|
+
previewImageAltText?: string | undefined;
|
|
78
82
|
/**
|
|
79
83
|
* Processed favicon images
|
|
80
84
|
*/
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
/** @type {{ config: MetaConfig }} */
|
|
13
13
|
let { config } = $props();
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
config;
|
|
15
|
+
let { themeColor, statusBarStyle, name, shortName, disablePageZoom } =
|
|
16
|
+
$derived(config);
|
|
17
17
|
|
|
18
18
|
let shouldSetTitle = $state(false);
|
|
19
19
|
|
|
@@ -4,7 +4,7 @@ type PWA = {
|
|
|
4
4
|
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
5
|
};
|
|
6
6
|
declare const PWA: import("svelte").Component<{
|
|
7
|
-
config: import("../
|
|
7
|
+
config: import("../typedef.js").MetaConfig;
|
|
8
8
|
}, {}, "">;
|
|
9
9
|
type $$ComponentProps = {
|
|
10
10
|
config: {
|
|
@@ -66,15 +66,19 @@ type $$ComponentProps = {
|
|
|
66
66
|
*/
|
|
67
67
|
disablePageZoom: boolean;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Landscape SEO image URL (1200×630)
|
|
70
70
|
*/
|
|
71
|
-
|
|
71
|
+
previewImageLandscape?: import("../../config/typedef.js").ImageSource | undefined;
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* Square SEO image URL (1200×1200)
|
|
74
|
+
*/
|
|
75
|
+
previewImageSquare?: import("../../config/typedef.js").ImageSource | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* - Alt text for social media image
|
|
74
78
|
*
|
|
75
79
|
* Favicon images (processed by imagetools)
|
|
76
80
|
*/
|
|
77
|
-
|
|
81
|
+
previewImageAltText?: string | undefined;
|
|
78
82
|
/**
|
|
79
83
|
* Processed favicon images
|
|
80
84
|
*/
|
|
@@ -11,25 +11,35 @@
|
|
|
11
11
|
* SEO component props
|
|
12
12
|
*
|
|
13
13
|
* @typedef {Object} SEOProps
|
|
14
|
+
*
|
|
14
15
|
* @property {MetaConfig} config - Configuration object
|
|
16
|
+
*
|
|
15
17
|
* @property {string} [title] - Page title (defaults to config name)
|
|
18
|
+
*
|
|
16
19
|
* @property {string} [description]
|
|
17
20
|
* Page description (defaults to config description)
|
|
18
|
-
*
|
|
19
|
-
* @property {string} [
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
|
+
* @property {string} [canonicalUrl]
|
|
23
|
+
* Canonical URL for this content to prevent duplicate content penalties
|
|
24
|
+
*
|
|
25
|
+
* @property {import('../../config/typedef.js').ImageSource}
|
|
26
|
+
* [previewImageLandscape] - Landscape preview image (1200×630)
|
|
27
|
+
*
|
|
28
|
+
* @property {import('../../config/typedef.js').ImageSource}
|
|
29
|
+
* [previewImageSquare] - Square preview image (1200×1200)
|
|
30
|
+
*
|
|
31
|
+
* @property {string} [previewImageAltText] - Alt text for preview images
|
|
32
|
+
*
|
|
22
33
|
* @property {string} [type] - Open Graph type (default: 'website')
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Site name for Open Graph (defaults to config name)
|
|
34
|
+
*
|
|
35
|
+
* @property {string} [locale] - Content locale (defaults to config locale)
|
|
36
|
+
*
|
|
27
37
|
* @property {Record<string, string>} [alternateUrls]
|
|
28
38
|
* Alternate language URLs for hreflang tags
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
39
|
+
*
|
|
40
|
+
* @property {string} [robots] - Robots meta directives
|
|
41
|
+
*
|
|
42
|
+
* @property {boolean} [noAiTraining] - Block AI training on this page
|
|
33
43
|
*/
|
|
34
44
|
|
|
35
45
|
/** @type {SEOProps} */
|
|
@@ -37,110 +47,136 @@
|
|
|
37
47
|
config,
|
|
38
48
|
title,
|
|
39
49
|
description,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
canonicalUrl = undefined,
|
|
51
|
+
|
|
52
|
+
previewImageLandscape,
|
|
53
|
+
previewImageSquare,
|
|
54
|
+
previewImageAltText,
|
|
55
|
+
|
|
43
56
|
type = 'website',
|
|
44
57
|
locale = undefined,
|
|
45
|
-
siteName,
|
|
46
58
|
alternateUrls = undefined,
|
|
47
59
|
robots = undefined,
|
|
48
60
|
noAiTraining = false
|
|
49
61
|
} = $props();
|
|
50
62
|
|
|
51
63
|
// Extract config values
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
let defaultLocale = $derived(config.defaultLocale);
|
|
65
|
+
let languages = $derived(config.languages);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get first image source from imageSource array
|
|
69
|
+
*
|
|
70
|
+
* @param {ImageSource|undefined|null} imageSource
|
|
71
|
+
*
|
|
72
|
+
* @returns {string|null} image source url
|
|
73
|
+
*
|
|
74
|
+
* @throws {Error} invalid parameter
|
|
75
|
+
*/
|
|
76
|
+
function imageSourceToSrc( imageSource ) {
|
|
77
|
+
if( !imageSource ) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if( !Array.isArray( imageSource ) || !imageSource[0] || !imageSource[0].src ) {
|
|
82
|
+
throw new Error('Invalid [imageSource]');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return imageSource[0].src ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let imageLandscape = $derived.by( () => {
|
|
89
|
+
let image = previewImageLandscape ?? config.previewImageLandscape;
|
|
90
|
+
|
|
91
|
+
// console.log("image", image);
|
|
92
|
+
|
|
93
|
+
return imageSourceToSrc(image);
|
|
94
|
+
} );
|
|
95
|
+
|
|
96
|
+
// console.log("imageLandscape", imageLandscape);
|
|
97
|
+
|
|
98
|
+
let imageSquare = $derived.by( () => {
|
|
99
|
+
let image = previewImageSquare ?? config.previewImageSquare;
|
|
100
|
+
|
|
101
|
+
return imageSourceToSrc(image);
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
let imageAltText = $derived(previewImageAltText ?? config.name);
|
|
105
|
+
|
|
106
|
+
let pageTitle = $derived(title ?? config.name);
|
|
107
|
+
let pageDescription = $derived(description ?? config.description);
|
|
108
|
+
|
|
109
|
+
let currentLocale = $derived(locale ?? defaultLocale);
|
|
73
110
|
</script>
|
|
74
111
|
|
|
75
112
|
<svelte:head>
|
|
76
|
-
<!-- Page title (overrides %title% from app.html) -->
|
|
77
|
-
<title>{
|
|
113
|
+
<!-- Basic SEO: Page title (overrides %title% from app.html) -->
|
|
114
|
+
<title>{pageTitle}</title>
|
|
78
115
|
|
|
79
|
-
<!-- Basic SEO -->
|
|
80
|
-
|
|
116
|
+
<!-- Basic SEO: Page title (overrides %title% from app.html) -->
|
|
117
|
+
{#if pageDescription}
|
|
118
|
+
<meta name="description" content={pageDescription} />
|
|
119
|
+
{/if}
|
|
120
|
+
|
|
121
|
+
{#if canonicalUrl}
|
|
122
|
+
<link rel="canonical" href={canonicalUrl} />
|
|
123
|
+
<meta property="og:url" content={canonicalUrl} />
|
|
124
|
+
{/if}
|
|
81
125
|
|
|
82
126
|
<!-- Robots directives -->
|
|
83
127
|
{#if robots}
|
|
84
|
-
<meta name="robots" content={robots}
|
|
128
|
+
<meta name="robots" content={robots} />
|
|
85
129
|
{/if}
|
|
86
130
|
|
|
87
131
|
<!-- AI training and reading restrictions -->
|
|
88
132
|
{#if noAiTraining}
|
|
89
133
|
<!-- Google AI -->
|
|
90
|
-
<meta name="google-extended" content="noindex, nofollow"
|
|
134
|
+
<meta name="google-extended" content="noindex, nofollow" />
|
|
91
135
|
<!-- OpenAI (ChatGPT) -->
|
|
92
|
-
<meta name="OAI-SearchBot" content="noindex, nofollow"
|
|
136
|
+
<meta name="OAI-SearchBot" content="noindex, nofollow" />
|
|
93
137
|
<!-- Common Crawl -->
|
|
94
|
-
<meta name="CCBot" content="noindex, nofollow"
|
|
138
|
+
<meta name="CCBot" content="noindex, nofollow" />
|
|
95
139
|
<!-- Anthropic Claude -->
|
|
96
|
-
<meta name="anthropic-ai" content="noindex, nofollow"
|
|
140
|
+
<meta name="anthropic-ai" content="noindex, nofollow" />
|
|
97
141
|
<!-- General AI crawlers -->
|
|
98
|
-
<meta name="robots" content="noai, noimageai"
|
|
142
|
+
<meta name="robots" content="noai, noimageai" />
|
|
99
143
|
{/if}
|
|
100
144
|
|
|
101
145
|
<!-- Open Graph / Facebook / LinkedIn / Discord -->
|
|
102
|
-
<meta property="og:type" content={type}
|
|
103
|
-
<meta property="og:
|
|
104
|
-
<meta property="og:
|
|
105
|
-
<meta property="og:
|
|
106
|
-
<meta property="og:locale" content={
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<
|
|
146
|
+
<meta property="og:type" content={type} />
|
|
147
|
+
<meta property="og:site_name" content={config.name} />
|
|
148
|
+
<meta property="og:title" content={pageTitle} />
|
|
149
|
+
<meta property="og:description" content={pageDescription} />
|
|
150
|
+
<meta property="og:locale" content={currentLocale} />
|
|
151
|
+
|
|
152
|
+
<!-- Default seo/social image (landscape) -->
|
|
153
|
+
{#if imageLandscape}
|
|
154
|
+
<meta property="og:image" content={imageLandscape} />
|
|
155
|
+
<meta property="og:image:alt" content={imageAltText} />
|
|
156
|
+
<meta property="og:image:width" content="1200" />
|
|
157
|
+
<meta property="og:image:height" content="630" />
|
|
158
|
+
<meta property="og:image:type" content="image/jpeg" />
|
|
111
159
|
{/if}
|
|
112
160
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<meta property="og:image
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<meta property="og:image:height" content="630">
|
|
120
|
-
<meta property="og:image:type" content="image/jpeg">
|
|
121
|
-
{/if}
|
|
122
|
-
{/if}
|
|
123
|
-
|
|
124
|
-
<!-- Additional square image for platforms that prefer it -->
|
|
125
|
-
{#if SeoImageSquare && SeoImageSquare !== image}
|
|
126
|
-
<meta property="og:image" content={SeoImageSquare}>
|
|
127
|
-
<meta property="og:image:width" content="1200">
|
|
128
|
-
<meta property="og:image:height" content="1200">
|
|
129
|
-
<meta property="og:image:type" content="image/jpeg">
|
|
161
|
+
<!-- Additional image for platforms that prefer square images -->
|
|
162
|
+
{#if imageSquare}
|
|
163
|
+
<meta property="og:image" content={imageSquare} />
|
|
164
|
+
<meta property="og:image:width" content="1200" />
|
|
165
|
+
<meta property="og:image:height" content="1200" />
|
|
166
|
+
<meta property="og:image:type" content="image/jpeg" />
|
|
130
167
|
{/if}
|
|
131
168
|
|
|
132
169
|
<!-- Alternate locales for Open Graph -->
|
|
133
170
|
{#each Object.entries(languages) as [code, config]}
|
|
134
|
-
{#if config.locale !==
|
|
135
|
-
<meta property="og:locale:alternate" content={config.locale}
|
|
171
|
+
{#if config.locale !== currentLocale}
|
|
172
|
+
<meta property="og:locale:alternate" content={config.locale} />
|
|
136
173
|
{/if}
|
|
137
174
|
{/each}
|
|
138
175
|
|
|
139
176
|
<!-- Alternate language URLs (hreflang) -->
|
|
140
177
|
{#if alternateUrls}
|
|
141
178
|
{#each Object.entries(alternateUrls) as [lang, href]}
|
|
142
|
-
<link rel="alternate" hreflang={lang}
|
|
179
|
+
<link rel="alternate" hreflang={lang} {href} />
|
|
143
180
|
{/each}
|
|
144
181
|
{/if}
|
|
145
|
-
|
|
146
182
|
</svelte:head>
|