@hkdigital/lib-core 0.5.96 → 0.5.97

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 (33) hide show
  1. package/README.md +8 -56
  2. package/dist/config/generators/imagetools.d.ts +4 -10
  3. package/dist/config/generators/imagetools.js +43 -25
  4. package/dist/config/imagetools.d.ts +2 -2
  5. package/dist/meta/components/Favicons.svelte +1 -1
  6. package/dist/meta/components/Favicons.svelte.d.ts +9 -5
  7. package/dist/meta/components/PWA.svelte +2 -2
  8. package/dist/meta/components/PWA.svelte.d.ts +9 -5
  9. package/dist/meta/components/SEO.svelte +113 -77
  10. package/dist/meta/components/SEO.svelte.d.ts +28 -28
  11. package/dist/{config/meta.d.ts → meta/config.d.ts} +8 -8
  12. package/dist/{config/meta.js → meta/config.js} +6 -6
  13. package/dist/meta/templates/README.md +3 -3
  14. package/dist/meta/templates/lib/{config/meta.d.ts → meta/config.d.ts} +22 -29
  15. package/dist/meta/templates/lib/{config/meta.js → meta/config.js} +48 -39
  16. package/dist/meta/templates/lib/meta/favicon.png +0 -0
  17. package/dist/meta/templates/lib/meta/preview-landscape.png +0 -0
  18. package/dist/meta/templates/lib/meta/preview-square.png +0 -0
  19. package/dist/meta/templates/routes/(meta)/manifest.json/+server.js +1 -1
  20. package/dist/meta/templates/routes/(meta)/robots.txt/+server.js +1 -1
  21. package/dist/meta/templates/routes/(meta)/sitemap.xml/+server.js +1 -1
  22. package/dist/meta/typedef.d.ts +100 -1
  23. package/dist/meta/typedef.js +45 -5
  24. package/dist/meta.d.ts +1 -1
  25. package/dist/meta.js +1 -1
  26. package/package.json +1 -1
  27. package/dist/meta/config.typedef.d.ts +0 -98
  28. package/dist/meta/config.typedef.js +0 -44
  29. package/dist/meta/templates/lib/meta.d.ts +0 -8
  30. package/dist/meta/templates/lib/meta.js +0 -23
  31. /package/dist/meta/{templates/lib/assets/meta/favicon.png → favicon.png} +0 -0
  32. /package/dist/meta/{templates/lib/assets/meta/preview-landscape.png → preview-landscape.png} +0 -0
  33. /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** - Configure SEO, PWA, and favicons:
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
- **Copy template files to your project:**
212
+ **Quick setup:**
213
213
  ```bash
214
- # Copy configuration and assets to src/lib/
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
- **Customize for your app:**
222
- - Replace images in `src/lib/assets/meta/` with your own:
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
- **What you get:**
262
- - Automatic favicon generation (16, 32, 192, 512px)
263
- - Apple touch icons (120, 152, 167, 180px)
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 {{width: number, height: number}} [options.seoLandscapeSize]
11
+ * @param {[number,number][]} [options.seoLandscapeSizes]
12
12
  * SEO landscape image size
13
13
  *
14
- * @param {{width: number, height: number}} [options.seoSquareSize]
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
- seoLandscapeSize?: {
27
- width: number;
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 SEO_LANDSCAPE_SIZE = { width: 1200, height: 630 };
25
- const SEO_SQUARE_SIZE = { width: 1200, height: 1200 };
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 {{width: number, height: number}} [options.seoLandscapeSize]
83
+ * @param {[number,number][]} [options.seoLandscapeSizes]
77
84
  * SEO landscape image size
78
85
  *
79
- * @param {{width: number, height: number}} [options.seoSquareSize]
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 seoLandscapeSize = options?.seoLandscapeSize ?? SEO_LANDSCAPE_SIZE;
139
- const seoSquareSize = options?.seoSquareSize ?? SEO_SQUARE_SIZE;
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
- return [{
189
- ...configPairs,
190
- w: String(seoLandscapeSize.width),
191
- h: String(seoLandscapeSize.height),
192
- format: 'jpg',
193
- quality: '95'
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
- return [{
201
- ...configPairs,
202
- w: String(seoSquareSize.width),
203
- h: String(seoSquareSize.height),
204
- format: 'jpg',
205
- quality: '95'
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
- // > Return metadata if directive 'responsive' is set
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 picture (URL) if directive 'seo-landscape' is set
291
+ // > Return metadata if directive 'seo-landscape' is set
274
292
 
275
293
  if (params.has('seo-landscape')) {
276
- params.set('as', 'picture');
294
+ params.set('as', 'metadata');
277
295
  }
278
296
 
279
- // > Return picture (URL) if directive 'seo-square' is set
297
+ // > Return metadata if directive 'seo-square' is set
280
298
 
281
299
  if (params.has('seo-square')) {
282
- params.set('as', 'picture');
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: string;
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: string;
96
+ const out: ImageSource;
97
97
  export default out;
98
98
  }
@@ -10,7 +10,7 @@
10
10
  /** @type {{ config: MetaConfig }} */
11
11
  let { config } = $props();
12
12
 
13
- const { faviconImages, appleTouchIcons } = config;
13
+ let { faviconImages, appleTouchIcons } = $derived(config);
14
14
  </script>
15
15
 
16
16
  <svelte:head>
@@ -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("../config.typedef.js").MetaConfig;
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
- * - Landscape SEO image URL (1200×630)
69
+ * Landscape SEO image URL (1200×630)
70
70
  */
71
- SeoImageLandscape?: string | undefined;
71
+ previewImageLandscape?: import("../../config/typedef.js").ImageSource | undefined;
72
72
  /**
73
- * - Square SEO image URL (1200×1200)
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
- SeoImageSquare?: string | undefined;
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
- const { themeColor, statusBarStyle, name, shortName, disablePageZoom } =
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("../config.typedef.js").MetaConfig;
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
- * - Landscape SEO image URL (1200×630)
69
+ * Landscape SEO image URL (1200×630)
70
70
  */
71
- SeoImageLandscape?: string | undefined;
71
+ previewImageLandscape?: import("../../config/typedef.js").ImageSource | undefined;
72
72
  /**
73
- * - Square SEO image URL (1200×1200)
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
- SeoImageSquare?: string | undefined;
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
- * @property {string} [url] - Canonical URL for this page
19
- * @property {string} [image]
20
- * Social media preview image URL (defaults to landscape SEO image)
21
- * @property {string} [imageAlt] - Alt text for social media image
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
- * @property {string} [locale]
24
- * Content locale (auto-detected from URL or defaults to config)
25
- * @property {string} [siteName]
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
- * @property {string} [robots]
30
- * Robots meta directives (e.g., 'noindex, nofollow')
31
- * @property {boolean} [noAiTraining]
32
- * Prevent AI training on this page content
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
- url = undefined,
41
- image,
42
- imageAlt,
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
- const {
53
- name: defaultTitle,
54
- description: defaultDescription,
55
- SeoImageLandscape,
56
- SeoImageSquare,
57
- defaultLocale,
58
- languages
59
- } = config;
60
-
61
- // Use the landscape image as default (best for most platforms)
62
- const defaultSeoImage = SeoImageLandscape || undefined;
63
-
64
- // Apply defaults from config
65
- title = title ?? defaultTitle;
66
- description = description ?? defaultDescription;
67
- image = image ?? defaultSeoImage;
68
- imageAlt = imageAlt ?? title;
69
- siteName = siteName ?? defaultTitle;
70
-
71
- // Use locale from prop or fall back to config default
72
- const finalLocale = locale || defaultLocale;
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>{title}</title>
113
+ <!-- Basic SEO: Page title (overrides %title% from app.html) -->
114
+ <title>{pageTitle}</title>
78
115
 
79
- <!-- Basic SEO -->
80
- <meta name="description" content={description}>
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:title" content={title}>
104
- <meta property="og:description" content={description}>
105
- <meta property="og:site_name" content={siteName}>
106
- <meta property="og:locale" content={finalLocale}>
107
-
108
- {#if url}
109
- <meta property="og:url" content={url}>
110
- <link rel="canonical" href={url}>
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
- {#if image}
114
- <meta property="og:image" content={image}>
115
- <meta property="og:image:alt" content={imageAlt}>
116
- <!-- Image metadata (dimensions from preprocessor) -->
117
- {#if image === SeoImageLandscape}
118
- <meta property="og:image:width" content="1200">
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 !== finalLocale}
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} href={href}>
179
+ <link rel="alternate" hreflang={lang} {href} />
143
180
  {/each}
144
181
  {/if}
145
-
146
182
  </svelte:head>