@simple-photo-gallery/theme-modern 2.0.16 → 2.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/features/themes/base-theme/components/footer/Footer.astro +1 -1
- package/src/features/themes/base-theme/components/gallery-section/GallerySection.astro +1 -1
- package/src/features/themes/base-theme/components/hero/Hero.astro +44 -52
- package/src/features/themes/base-theme/layouts/MainHead.astro +56 -20
- package/src/features/themes/base-theme/layouts/MainLayout.astro +5 -2
- package/src/features/themes/base-theme/pages/index.astro +4 -1
- package/src/features/themes/base-theme/utils/index.ts +43 -0
- package/src/features/themes/base-theme/utils/queryParams.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simple-photo-gallery/theme-modern",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
4
4
|
"description": "Modern theme for Simple Photo Gallery",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vladimir Haltakov, Tomasz Rusin",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/eslintrc": "^3.3.1",
|
|
38
38
|
"@eslint/js": "^9.30.1",
|
|
39
|
-
"@simple-photo-gallery/common": "1.0.
|
|
39
|
+
"@simple-photo-gallery/common": "1.0.6",
|
|
40
40
|
"@types/photoswipe": "^4.1.6",
|
|
41
41
|
"@typescript-eslint/eslint-plugin": "^8.35.1",
|
|
42
42
|
"@typescript-eslint/parser": "^8.35.1",
|
|
@@ -40,7 +40,7 @@ const validImages = section.images.filter(
|
|
|
40
40
|
<style>
|
|
41
41
|
.gallery-section {
|
|
42
42
|
padding: 1rem 1rem;
|
|
43
|
-
background-color: var(--section-bg-color-odd, var(--section-bg-color,
|
|
43
|
+
background-color: var(--section-bg-color-odd, var(--section-bg-color, #ffffff));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
.gallery-section:nth-child(even) {
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { getPhotoPath } from '../../utils';
|
|
4
|
+
import { buildHeroSrcset, getPhotoPath, LANDSCAPE_SIZES, PORTRAIT_SIZES } from '../../utils';
|
|
5
5
|
|
|
6
6
|
import HeroScrollToGalleryBtn from '@/features/themes/base-theme/components/hero/HeroScrollToGalleryBtn.astro';
|
|
7
7
|
import { renderMarkdown } from '@/lib/markdown';
|
|
8
8
|
|
|
9
|
+
import type { HeaderImageVariants } from '@simple-photo-gallery/common';
|
|
10
|
+
|
|
9
11
|
interface Props {
|
|
10
12
|
title: string;
|
|
11
13
|
description?: string;
|
|
12
14
|
thumbsBaseUrl?: string;
|
|
13
15
|
headerImage?: string;
|
|
14
16
|
headerImageBlurHash?: string;
|
|
17
|
+
headerImageVariants?: HeaderImageVariants;
|
|
15
18
|
mediaBaseUrl?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
const { title, description, thumbsBaseUrl, headerImage, headerImageBlurHash, mediaBaseUrl } =
|
|
21
|
+
const { title, description, thumbsBaseUrl, headerImage, headerImageBlurHash, headerImageVariants, mediaBaseUrl } =
|
|
22
|
+
Astro.props;
|
|
19
23
|
|
|
20
24
|
// Parse description as Markdown if it exists
|
|
21
25
|
const parsedDescription: string = description ? await renderMarkdown(description) : '';
|
|
@@ -28,59 +32,47 @@ const thumbnailBasePath = thumbsBaseUrl || 'gallery/images';
|
|
|
28
32
|
|
|
29
33
|
// Original header photo
|
|
30
34
|
const headerPhotoPath = getPhotoPath(headerImage || '', mediaBaseUrl);
|
|
35
|
+
|
|
36
|
+
// Determine which sources to show based on headerImageVariants
|
|
37
|
+
// If headerImageVariants is not set, use all generated paths (default behavior)
|
|
38
|
+
// If headerImageVariants is set, only show sources for formats that are explicitly provided
|
|
39
|
+
const useDefaultPaths = !headerImageVariants;
|
|
40
|
+
|
|
41
|
+
// Pre-compute all srcsets - only render sources when srcset is non-empty
|
|
42
|
+
// This handles edge cases like empty format objects: { avif: {} }
|
|
43
|
+
const portraitAvifSrcset = buildHeroSrcset(headerImageVariants?.portrait?.avif, PORTRAIT_SIZES, thumbnailBasePath, imgBasename, 'portrait', 'avif', useDefaultPaths);
|
|
44
|
+
const portraitJpgSrcset = buildHeroSrcset(headerImageVariants?.portrait?.jpg, PORTRAIT_SIZES, thumbnailBasePath, imgBasename, 'portrait', 'jpg', useDefaultPaths);
|
|
45
|
+
const landscapeAvifSrcset = buildHeroSrcset(headerImageVariants?.landscape?.avif, LANDSCAPE_SIZES, thumbnailBasePath, imgBasename, 'landscape', 'avif', useDefaultPaths);
|
|
46
|
+
const landscapeJpgSrcset = buildHeroSrcset(headerImageVariants?.landscape?.jpg, LANDSCAPE_SIZES, thumbnailBasePath, imgBasename, 'landscape', 'jpg', useDefaultPaths);
|
|
31
47
|
---
|
|
32
48
|
|
|
33
49
|
<section class="hero">
|
|
34
50
|
<div class="hero__bg-wrapper">
|
|
35
51
|
{headerImageBlurHash && <canvas data-blur-hash={headerImageBlurHash} width={32} height={32} />}
|
|
36
52
|
<picture class="hero__bg" id="hero-bg-picture">
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
/>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<source
|
|
61
|
-
type="image/avif"
|
|
62
|
-
srcset={`
|
|
63
|
-
${thumbnailBasePath}/${imgBasename}_landscape_640.avif 640w,
|
|
64
|
-
${thumbnailBasePath}/${imgBasename}_landscape_960.avif 960w,
|
|
65
|
-
${thumbnailBasePath}/${imgBasename}_landscape_1280.avif 1280w,
|
|
66
|
-
${thumbnailBasePath}/${imgBasename}_landscape_1920.avif 1920w,
|
|
67
|
-
${thumbnailBasePath}/${imgBasename}_landscape_2560.avif 2560w,
|
|
68
|
-
${thumbnailBasePath}/${imgBasename}_landscape_3840.avif 3840w`}
|
|
69
|
-
sizes="100vw"
|
|
70
|
-
/>
|
|
71
|
-
<source
|
|
72
|
-
type="image/jpg"
|
|
73
|
-
srcset={`
|
|
74
|
-
${thumbnailBasePath}/${imgBasename}_landscape_640.jpg 640w,
|
|
75
|
-
${thumbnailBasePath}/${imgBasename}_landscape_960.jpg 960w,
|
|
76
|
-
${thumbnailBasePath}/${imgBasename}_landscape_1280.jpg 1280w,
|
|
77
|
-
${thumbnailBasePath}/${imgBasename}_landscape_1920.jpg 1920w,
|
|
78
|
-
${thumbnailBasePath}/${imgBasename}_landscape_2560.jpg 2560w,
|
|
79
|
-
${thumbnailBasePath}/${imgBasename}_landscape_3840.jpg 3840w`}
|
|
80
|
-
sizes="100vw"
|
|
81
|
-
/>
|
|
82
|
-
|
|
83
|
-
<!-- Fallback -->
|
|
53
|
+
{/* Portrait */}
|
|
54
|
+
{portraitAvifSrcset && (
|
|
55
|
+
<source
|
|
56
|
+
type="image/avif"
|
|
57
|
+
media="(max-aspect-ratio: 3/4)"
|
|
58
|
+
srcset={portraitAvifSrcset}
|
|
59
|
+
sizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
{portraitJpgSrcset && (
|
|
63
|
+
<source
|
|
64
|
+
type="image/jpeg"
|
|
65
|
+
media="(max-aspect-ratio: 3/4)"
|
|
66
|
+
srcset={portraitJpgSrcset}
|
|
67
|
+
sizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{/* Landscape */}
|
|
72
|
+
{landscapeAvifSrcset && <source type="image/avif" srcset={landscapeAvifSrcset} sizes="100vw" />}
|
|
73
|
+
{landscapeJpgSrcset && <source type="image/jpeg" srcset={landscapeJpgSrcset} sizes="100vw" />}
|
|
74
|
+
|
|
75
|
+
{/* Fallback */}
|
|
84
76
|
<img src={headerPhotoPath} class="hero__bg-img" />
|
|
85
77
|
</picture>
|
|
86
78
|
<script is:inline>
|
|
@@ -145,13 +137,13 @@ const headerPhotoPath = getPhotoPath(headerImage || '', mediaBaseUrl);
|
|
|
145
137
|
() => {
|
|
146
138
|
// Final fallback failed, blurhash stays visible
|
|
147
139
|
},
|
|
148
|
-
{ once: true }
|
|
140
|
+
{ once: true },
|
|
149
141
|
);
|
|
150
142
|
|
|
151
143
|
// If fallback succeeds, hide blurhash
|
|
152
144
|
img.addEventListener('load', hideBlurhash, { once: true });
|
|
153
145
|
},
|
|
154
|
-
{ once: true }
|
|
146
|
+
{ once: true },
|
|
155
147
|
);
|
|
156
148
|
})();
|
|
157
149
|
</script>
|
|
@@ -168,7 +160,7 @@ const headerPhotoPath = getPhotoPath(headerImage || '', mediaBaseUrl);
|
|
|
168
160
|
.hero {
|
|
169
161
|
position: relative;
|
|
170
162
|
min-height: 450px;
|
|
171
|
-
height: 100vh;
|
|
163
|
+
height: var(--hero-height, 100vh);
|
|
172
164
|
display: flex;
|
|
173
165
|
align-items: center;
|
|
174
166
|
justify-content: center;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import { buildHeroSrcset, LANDSCAPE_SIZES, PORTRAIT_SIZES } from '../utils';
|
|
3
|
+
|
|
4
|
+
import type { GalleryMetadata, HeaderImageVariants } from '@simple-photo-gallery/common/src/gallery';
|
|
3
5
|
|
|
4
6
|
interface Props {
|
|
5
7
|
title: string;
|
|
@@ -8,15 +10,45 @@ interface Props {
|
|
|
8
10
|
thumbsBaseUrl?: string;
|
|
9
11
|
metadata?: GalleryMetadata;
|
|
10
12
|
headerImageBasename?: string;
|
|
13
|
+
headerImageVariants?: HeaderImageVariants;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
const { title, description, url, thumbsBaseUrl, metadata, headerImageBasename } = Astro.props;
|
|
16
|
+
const { title, description, url, thumbsBaseUrl, metadata, headerImageBasename, headerImageVariants } = Astro.props;
|
|
14
17
|
|
|
15
18
|
// Use headerImageBasename for dynamic image paths, fallback to generic name
|
|
16
19
|
const imgBasename = headerImageBasename || 'header';
|
|
17
20
|
|
|
18
21
|
// Get the base path for the thumbnails
|
|
19
22
|
const thumbnailBasePath = thumbsBaseUrl || 'gallery/images';
|
|
23
|
+
|
|
24
|
+
// Determine which sources to show based on headerImageVariants
|
|
25
|
+
// If headerImageVariants is not set, use all generated paths (default behavior)
|
|
26
|
+
// If headerImageVariants is set, only show sources for formats that are explicitly provided
|
|
27
|
+
const useDefaultPaths = !headerImageVariants;
|
|
28
|
+
|
|
29
|
+
// Pre-compute srcsets - only non-empty srcsets will be rendered
|
|
30
|
+
// This handles edge cases like empty format objects: { avif: {} }
|
|
31
|
+
const portraitAvifSrcset = buildHeroSrcset(headerImageVariants?.portrait?.avif, PORTRAIT_SIZES, thumbnailBasePath, imgBasename, 'portrait', 'avif', useDefaultPaths);
|
|
32
|
+
const portraitJpgSrcset = buildHeroSrcset(headerImageVariants?.portrait?.jpg, PORTRAIT_SIZES, thumbnailBasePath, imgBasename, 'portrait', 'jpg', useDefaultPaths);
|
|
33
|
+
const landscapeAvifSrcset = buildHeroSrcset(headerImageVariants?.landscape?.avif, LANDSCAPE_SIZES, thumbnailBasePath, imgBasename, 'landscape', 'avif', useDefaultPaths);
|
|
34
|
+
const landscapeJpgSrcset = buildHeroSrcset(headerImageVariants?.landscape?.jpg, LANDSCAPE_SIZES, thumbnailBasePath, imgBasename, 'landscape', 'jpg', useDefaultPaths);
|
|
35
|
+
|
|
36
|
+
// Determine media queries for preloads to match actual <source> media attributes in Hero.astro
|
|
37
|
+
// Portrait <source> always has media="(max-aspect-ratio: 3/4)", so preload must use the same restriction
|
|
38
|
+
// Otherwise landscape devices would preload portrait images they'll never use (wasting bandwidth)
|
|
39
|
+
// Landscape sources have no media restriction in Hero.astro, but we add one here when ANY portrait source exists
|
|
40
|
+
// to prevent landscape images from being preloaded on portrait devices (which will use portrait sources)
|
|
41
|
+
// We check both AVIF and JPG because portrait devices will use whichever portrait format is available
|
|
42
|
+
const hasAnyPortraitSource = portraitAvifSrcset || portraitJpgSrcset;
|
|
43
|
+
const portraitPreloadMedia = '(max-aspect-ratio: 3/4)';
|
|
44
|
+
const landscapePreloadMedia = hasAnyPortraitSource ? 'not (max-aspect-ratio: 3/4)' : undefined;
|
|
45
|
+
|
|
46
|
+
// Determine which format to preload for each orientation
|
|
47
|
+
// Prefer AVIF when available (better compression), fall back to JPG for JPG-only configurations
|
|
48
|
+
const portraitPreloadSrcset = portraitAvifSrcset || portraitJpgSrcset;
|
|
49
|
+
const portraitPreloadType = portraitAvifSrcset ? 'image/avif' : 'image/jpeg';
|
|
50
|
+
const landscapePreloadSrcset = landscapeAvifSrcset || landscapeJpgSrcset;
|
|
51
|
+
const landscapePreloadType = landscapeAvifSrcset ? 'image/avif' : 'image/jpeg';
|
|
20
52
|
---
|
|
21
53
|
|
|
22
54
|
<head>
|
|
@@ -77,22 +109,26 @@ const thumbnailBasePath = thumbsBaseUrl || 'gallery/images';
|
|
|
77
109
|
href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap"
|
|
78
110
|
rel="stylesheet"
|
|
79
111
|
/>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
{portraitPreloadSrcset && (
|
|
113
|
+
<link
|
|
114
|
+
rel="preload"
|
|
115
|
+
as="image"
|
|
116
|
+
type={portraitPreloadType}
|
|
117
|
+
media={portraitPreloadMedia}
|
|
118
|
+
imagesrcset={portraitPreloadSrcset}
|
|
119
|
+
imagesizes="(max-aspect-ratio: 3/4) 160vw, 100vw"
|
|
120
|
+
fetchpriority="high"
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
{landscapePreloadSrcset && (
|
|
124
|
+
<link
|
|
125
|
+
rel="preload"
|
|
126
|
+
as="image"
|
|
127
|
+
type={landscapePreloadType}
|
|
128
|
+
media={landscapePreloadMedia}
|
|
129
|
+
imagesrcset={landscapePreloadSrcset}
|
|
130
|
+
imagesizes="100vw"
|
|
131
|
+
fetchpriority="high"
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
98
134
|
</head>
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import MainHead from '@/features/themes/base-theme/layouts/MainHead.astro';
|
|
5
5
|
|
|
6
|
-
import type { GalleryMetadata } from '@simple-photo-gallery/common/src/gallery';
|
|
6
|
+
import type { GalleryMetadata, HeaderImageVariants } from '@simple-photo-gallery/common/src/gallery';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
title: string;
|
|
@@ -13,9 +13,11 @@ interface Props {
|
|
|
13
13
|
metadata?: GalleryMetadata;
|
|
14
14
|
analyticsScript?: string;
|
|
15
15
|
headerImage?: string;
|
|
16
|
+
headerImageVariants?: HeaderImageVariants;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const { title, description, metadata, url, thumbsBaseUrl, analyticsScript, headerImage } =
|
|
19
|
+
const { title, description, metadata, url, thumbsBaseUrl, analyticsScript, headerImage, headerImageVariants } =
|
|
20
|
+
Astro.props;
|
|
19
21
|
|
|
20
22
|
// Extract basename from headerImage filename
|
|
21
23
|
const headerImageBasename = headerImage ? path.basename(headerImage, path.extname(headerImage)) : undefined;
|
|
@@ -30,6 +32,7 @@ const headerImageBasename = headerImage ? path.basename(headerImage, path.extnam
|
|
|
30
32
|
url={url}
|
|
31
33
|
thumbsBaseUrl={thumbsBaseUrl}
|
|
32
34
|
headerImageBasename={headerImageBasename}
|
|
35
|
+
headerImageVariants={headerImageVariants}
|
|
33
36
|
/>
|
|
34
37
|
<body>
|
|
35
38
|
<slot />
|
|
@@ -29,6 +29,7 @@ const {
|
|
|
29
29
|
analyticsScript,
|
|
30
30
|
headerImage,
|
|
31
31
|
headerImageBlurHash,
|
|
32
|
+
headerImageVariants,
|
|
32
33
|
ctaBanner,
|
|
33
34
|
} = gallery;
|
|
34
35
|
|
|
@@ -66,12 +67,14 @@ const showCtaBanner = ctaBanner === true;
|
|
|
66
67
|
url={url}
|
|
67
68
|
thumbsBaseUrl={thumbsBaseUrl}
|
|
68
69
|
analyticsScript={analyticsScript}
|
|
69
|
-
headerImage={headerImage}
|
|
70
|
+
headerImage={headerImage}
|
|
71
|
+
headerImageVariants={headerImageVariants}>
|
|
70
72
|
<Hero
|
|
71
73
|
title={title}
|
|
72
74
|
description={description}
|
|
73
75
|
headerImage={headerImage}
|
|
74
76
|
headerImageBlurHash={headerImageBlurHash}
|
|
77
|
+
headerImageVariants={headerImageVariants}
|
|
75
78
|
thumbsBaseUrl={thumbsBaseUrl}
|
|
76
79
|
mediaBaseUrl={mediaBaseUrl}
|
|
77
80
|
/>
|
|
@@ -62,3 +62,46 @@ export const getSubgalleryThumbnailPath = (subgalleryHeaderImagePath: string) =>
|
|
|
62
62
|
|
|
63
63
|
return path.join(subgalleryFolderName, 'gallery', 'thumbnails', photoBasename);
|
|
64
64
|
};
|
|
65
|
+
|
|
66
|
+
/** Portrait image sizes for responsive hero images */
|
|
67
|
+
export const PORTRAIT_SIZES = [360, 480, 720, 1080] as const;
|
|
68
|
+
|
|
69
|
+
/** Landscape image sizes for responsive hero images */
|
|
70
|
+
export const LANDSCAPE_SIZES = [640, 960, 1280, 1920, 2560, 3840] as const;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build a srcset string for responsive images.
|
|
74
|
+
* Uses custom paths from variants when provided, otherwise generates default paths.
|
|
75
|
+
*
|
|
76
|
+
* @param variants - Optional record mapping sizes to custom URLs
|
|
77
|
+
* @param sizes - Array of image widths to include
|
|
78
|
+
* @param thumbnailBasePath - Base path for generated thumbnails
|
|
79
|
+
* @param imgBasename - Image basename for generated paths
|
|
80
|
+
* @param orientation - 'portrait' or 'landscape'
|
|
81
|
+
* @param format - Image format ('avif' or 'jpg')
|
|
82
|
+
* @param useDefaultPaths - Whether to use generated paths when no custom variant exists
|
|
83
|
+
* @returns Comma-separated srcset string
|
|
84
|
+
*/
|
|
85
|
+
export const buildHeroSrcset = (
|
|
86
|
+
variants: Record<number, string | undefined> | undefined,
|
|
87
|
+
sizes: readonly number[],
|
|
88
|
+
thumbnailBasePath: string,
|
|
89
|
+
imgBasename: string,
|
|
90
|
+
orientation: 'portrait' | 'landscape',
|
|
91
|
+
format: 'avif' | 'jpg',
|
|
92
|
+
useDefaultPaths: boolean,
|
|
93
|
+
): string => {
|
|
94
|
+
return sizes
|
|
95
|
+
.map((size) => {
|
|
96
|
+
const customPath = variants?.[size];
|
|
97
|
+
if (customPath) {
|
|
98
|
+
return `${customPath} ${size}w`;
|
|
99
|
+
}
|
|
100
|
+
if (useDefaultPaths) {
|
|
101
|
+
return `${thumbnailBasePath}/${imgBasename}_${orientation}_${size}.${format} ${size}w`;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
})
|
|
105
|
+
.filter(Boolean)
|
|
106
|
+
.join(', ');
|
|
107
|
+
};
|
|
@@ -134,6 +134,16 @@ const applySectionBackgroundColors = (params: URLSearchParams): void => {
|
|
|
134
134
|
setCSSVar(root, '--section-bg-color-odd', parseColor(params.get('sectionBgColorOdd')));
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Applies hero height from the 'heroHeight' query parameter.
|
|
139
|
+
* Accepts a number (e.g., '100' for 100vh, '50' for 50vh).
|
|
140
|
+
*/
|
|
141
|
+
const applyHeroHeight = (params: URLSearchParams): void => {
|
|
142
|
+
const value = params.get('heroHeight')?.trim();
|
|
143
|
+
const height = value && /^\d+(\.\d+)?$/.test(value) ? `${value}vh` : null;
|
|
144
|
+
setCSSVar(document.documentElement, '--hero-height', height);
|
|
145
|
+
};
|
|
146
|
+
|
|
137
147
|
/**
|
|
138
148
|
* Main function that applies all query parameter configurations to the page.
|
|
139
149
|
* Reads URL search params and applies header visibility, background, typography, and section colors.
|
|
@@ -145,4 +155,5 @@ export const applyQueryParams = (): void => {
|
|
|
145
155
|
applyTransparentBackground(params);
|
|
146
156
|
applyTypographyColors(params);
|
|
147
157
|
applySectionBackgroundColors(params);
|
|
158
|
+
applyHeroHeight(params);
|
|
148
159
|
};
|