@eox/pages-theme-eox 1.9.0 → 2.0.0-alpha.10
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/.gitlab-ci.yml +4 -1
- package/README.md +88 -11
- package/cypress/support/mocks/authors.data.js +14 -0
- package/cypress/support/mocks/helpers.js +65 -4
- package/cypress/support/mocks/vitepress.js +8 -1
- package/cypress.config.js +14 -0
- package/package.json +3 -1
- package/src/Layout.vue +69 -1
- package/src/authors.data.js +14 -0
- package/src/components/BlogList.vue +655 -0
- package/src/components/BlogPost.vue +472 -0
- package/src/components/CTASection.vue +18 -4
- package/src/components/FeatureCard/Index.vue +462 -31
- package/src/components/FeatureCard/Poster.vue +25 -3
- package/src/components/FeatureSection.cy.js +16 -0
- package/src/components/FeatureSection.vue +3 -8
- package/src/components/FeaturesGallery.vue +34 -2
- package/src/components/Footer.cy.js +52 -2
- package/src/components/Footer.vue +17 -5
- package/src/components/HeroSection.cy.js +20 -0
- package/src/components/HeroSection.vue +140 -25
- package/src/components/ImageCarousel.vue +11 -2
- package/src/components/ImageElement.vue +16 -4
- package/src/components/ImageGallery.vue +29 -4
- package/src/components/LogoSection.vue +26 -3
- package/src/components/MobileNavDropdown.vue +13 -3
- package/src/components/NavBar.cy.js +15 -0
- package/src/components/NavBar.vue +149 -21
- package/src/components/NavDropdown.vue +12 -2
- package/src/components/OpenPositions.vue +292 -0
- package/src/components/SectionHeader.cy.js +54 -0
- package/src/components/SectionHeader.vue +115 -0
- package/src/components/TagList.vue +311 -0
- package/src/components/TeamPage.vue +347 -0
- package/src/components/TestimonialsSection.cy.js +76 -0
- package/src/components/TestimonialsSection.vue +368 -0
- package/src/components/TextSection.cy.js +115 -0
- package/src/components/TextSection.vue +396 -0
- package/src/helpers.js +182 -37
- package/src/helpers.test.js +181 -0
- package/src/index.js +16 -0
- package/src/pages/author/[author].md +5 -0
- package/src/pages/author/[author].paths.js +60 -0
- package/src/pages/blog.md +5 -0
- package/src/pages/tag/[tag].md +5 -0
- package/src/pages/tag/[tag].paths.js +49 -0
- package/src/pages/tag.md +5 -0
- package/src/pages/team.md +5 -0
- package/src/posts.data.js +134 -0
- package/src/style.css +389 -8
- package/src/tagUtils.js +148 -0
- package/src/team.data.js +1 -1
- package/src/vitepressConfig.mjs +101 -1
|
@@ -50,6 +50,7 @@ const { cards, sectionTitle, background, variant, columns } = defineProps({
|
|
|
50
50
|
|
|
51
51
|
const cardComponents = {
|
|
52
52
|
default: FeatureCardDefault,
|
|
53
|
+
cover: FeatureCardDefault,
|
|
53
54
|
poster: defineAsyncComponent(() => import("./FeatureCard/Poster.vue")),
|
|
54
55
|
};
|
|
55
56
|
|
|
@@ -63,9 +64,38 @@ const resolveCardComponent = (variant) => {
|
|
|
63
64
|
|
|
64
65
|
const { page, site } = useData();
|
|
65
66
|
|
|
67
|
+
function resolveUrl(url) {
|
|
68
|
+
if (!url) return "";
|
|
69
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
70
|
+
const base = withBase("/");
|
|
71
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
72
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeFeature(feature) {
|
|
76
|
+
return {
|
|
77
|
+
...feature,
|
|
78
|
+
content: feature.content || feature.description,
|
|
79
|
+
chips: feature.chips || feature.tags,
|
|
80
|
+
image: resolveUrl(feature.image),
|
|
81
|
+
link: feature.link
|
|
82
|
+
? {
|
|
83
|
+
...feature.link,
|
|
84
|
+
href: resolveUrl(feature.link.href),
|
|
85
|
+
}
|
|
86
|
+
: undefined,
|
|
87
|
+
secondaryLink: feature.secondaryLink
|
|
88
|
+
? {
|
|
89
|
+
...feature.secondaryLink,
|
|
90
|
+
href: resolveUrl(feature.secondaryLink.href),
|
|
91
|
+
}
|
|
92
|
+
: undefined,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
66
96
|
const featuresExcerpts =
|
|
67
97
|
cards && cards.length
|
|
68
|
-
? cards
|
|
98
|
+
? cards.map(normalizeFeature)
|
|
69
99
|
: features
|
|
70
100
|
.map((f) => {
|
|
71
101
|
if (import.meta.env.SSR) {
|
|
@@ -89,7 +119,7 @@ const featuresExcerpts =
|
|
|
89
119
|
}
|
|
90
120
|
: undefined,
|
|
91
121
|
link: {
|
|
92
|
-
href: f.url,
|
|
122
|
+
href: withBase(f.url),
|
|
93
123
|
text: "Read more",
|
|
94
124
|
target: "_self",
|
|
95
125
|
},
|
|
@@ -145,6 +175,7 @@ const gridCols = computed(() => {
|
|
|
145
175
|
:metadata="feature.metadata"
|
|
146
176
|
:link="feature.link"
|
|
147
177
|
:secondaryLink="feature.secondaryLink"
|
|
178
|
+
:variant="variant"
|
|
148
179
|
:class="`${cardBackground}`"
|
|
149
180
|
/>
|
|
150
181
|
</div>
|
|
@@ -159,6 +190,7 @@ const gridCols = computed(() => {
|
|
|
159
190
|
:metadata="feature.metadata"
|
|
160
191
|
:link="feature.link"
|
|
161
192
|
:secondaryLink="feature.secondaryLink"
|
|
193
|
+
:variant="variant"
|
|
162
194
|
:class="`${cardBackground}`"
|
|
163
195
|
/>
|
|
164
196
|
</div>
|
|
@@ -42,7 +42,7 @@ describe("<Footer />", () => {
|
|
|
42
42
|
|
|
43
43
|
it("renders legal links", () => {
|
|
44
44
|
cy.mount(Footer);
|
|
45
|
-
cy.contains("a", "
|
|
45
|
+
cy.contains("a", "Imprint").should("have.attr", "href", "/about");
|
|
46
46
|
cy.contains("a", "Terms & Conditions").should(
|
|
47
47
|
"have.attr",
|
|
48
48
|
"href",
|
|
@@ -51,6 +51,43 @@ describe("<Footer />", () => {
|
|
|
51
51
|
cy.contains("a", "Privacy").should("have.attr", "href", "/privacy");
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
it("renders uncategorized nav links under site title", () => {
|
|
55
|
+
cy.mount(Footer);
|
|
56
|
+
cy.contains("p.bold", "EOX Site").should("exist");
|
|
57
|
+
cy.contains("a", "Link 1").should("have.attr", "href", "/link1");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("renders uncategorized nav links under brandConfig title when present", () => {
|
|
61
|
+
__setMockData({
|
|
62
|
+
theme: {
|
|
63
|
+
theme: {
|
|
64
|
+
brandConfig: {
|
|
65
|
+
title: "Custom Brand",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
cy.mount(Footer);
|
|
71
|
+
cy.contains("p.bold", "Custom Brand").should("exist");
|
|
72
|
+
cy.contains("a", "Link 1").should("have.attr", "href", "/link1");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("renders dropdown items under category title", () => {
|
|
76
|
+
__setMockData({
|
|
77
|
+
theme: {
|
|
78
|
+
nav: [
|
|
79
|
+
{
|
|
80
|
+
text: "Our Company",
|
|
81
|
+
items: [{ text: "About", link: "/about" }],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
cy.mount(Footer);
|
|
87
|
+
cy.contains("p.bold", "Our Company").should("exist");
|
|
88
|
+
cy.contains("a", "About").should("have.attr", "href", "/about");
|
|
89
|
+
});
|
|
90
|
+
|
|
54
91
|
it("renders footer message with HTML content", () => {
|
|
55
92
|
__setMockData({
|
|
56
93
|
site: { title: "EOX Site" },
|
|
@@ -87,6 +124,7 @@ describe("<Footer />", () => {
|
|
|
87
124
|
},
|
|
88
125
|
i18n: {
|
|
89
126
|
About: "Über uns",
|
|
127
|
+
Imprint: "Impressum",
|
|
90
128
|
Legal: "Rechtliches",
|
|
91
129
|
"Powered by": "Unterstützt von",
|
|
92
130
|
},
|
|
@@ -96,7 +134,7 @@ describe("<Footer />", () => {
|
|
|
96
134
|
},
|
|
97
135
|
});
|
|
98
136
|
cy.mount(Footer);
|
|
99
|
-
cy.contains("
|
|
137
|
+
cy.contains("Impressum").should("exist");
|
|
100
138
|
cy.contains("Rechtliches").should("exist");
|
|
101
139
|
});
|
|
102
140
|
|
|
@@ -107,4 +145,16 @@ describe("<Footer />", () => {
|
|
|
107
145
|
.should("have.attr", "href", "https://eox.at")
|
|
108
146
|
.and("contain", "EOX");
|
|
109
147
|
});
|
|
148
|
+
|
|
149
|
+
it("defines hover underline styles for footer links", () => {
|
|
150
|
+
cy.mount(Footer);
|
|
151
|
+
cy.get("footer .link").should("have.css", "text-decoration-line", "none");
|
|
152
|
+
cy.document().then((doc) => {
|
|
153
|
+
const styles = Array.from(doc.querySelectorAll("style"))
|
|
154
|
+
.map((s) => s.textContent)
|
|
155
|
+
.join("\n");
|
|
156
|
+
expect(styles).to.include("footer .link:hover");
|
|
157
|
+
expect(styles).to.include("text-decoration: underline");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
110
160
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<footer class="surface-container-
|
|
2
|
+
<footer class="surface-container-lowest row center-align">
|
|
3
3
|
<div class="holder large-padding">
|
|
4
4
|
<div class="large-space"></div>
|
|
5
5
|
<div class="grid">
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
<a
|
|
15
15
|
v-if="theme.nav.find((i) => i.link && i.link.includes('contact'))"
|
|
16
16
|
:href="
|
|
17
|
-
|
|
17
|
+
withBase(
|
|
18
|
+
theme.nav.find((i) => i.link && i.link.includes('contact'))
|
|
19
|
+
.link,
|
|
20
|
+
)
|
|
18
21
|
"
|
|
19
22
|
class="button small border no-margin"
|
|
20
23
|
style="color: var(--on-surface)"
|
|
@@ -33,7 +36,9 @@
|
|
|
33
36
|
class="footer-column-item"
|
|
34
37
|
v-if="theme.nav.filter((i) => !i.action && i.link).length"
|
|
35
38
|
>
|
|
36
|
-
<p class="bold">
|
|
39
|
+
<p class="bold">
|
|
40
|
+
{{ theme.theme?.brandConfig?.title || site.title }}
|
|
41
|
+
</p>
|
|
37
42
|
<p v-for="item in theme.nav.filter((i) => !i.action && i.link)">
|
|
38
43
|
<a
|
|
39
44
|
:href="withBase(item.link)"
|
|
@@ -69,7 +74,7 @@
|
|
|
69
74
|
"
|
|
70
75
|
target="_blank"
|
|
71
76
|
class="link"
|
|
72
|
-
>{{ t("
|
|
77
|
+
>{{ t("Imprint", theme.i18n) }}</a
|
|
73
78
|
>
|
|
74
79
|
</p>
|
|
75
80
|
<p v-for="(term, index) in termsLinks" :key="term.id || index">
|
|
@@ -89,7 +94,7 @@
|
|
|
89
94
|
>
|
|
90
95
|
</p>
|
|
91
96
|
<p v-if="theme.theme.brandConfig?.analytics">
|
|
92
|
-
<a href="/cookie-settings" class="link">{{
|
|
97
|
+
<a :href="withBase('/cookie-settings')" class="link">{{
|
|
93
98
|
t("Cookie settings", theme.i18n)
|
|
94
99
|
}}</a>
|
|
95
100
|
</p>
|
|
@@ -133,10 +138,17 @@ footer .grid > div:not(:first-child) p,
|
|
|
133
138
|
margin: 0 !important;
|
|
134
139
|
}
|
|
135
140
|
|
|
141
|
+
footer a:not(.button),
|
|
136
142
|
footer .link {
|
|
137
143
|
text-decoration: none;
|
|
138
144
|
}
|
|
139
145
|
|
|
146
|
+
footer a:not(.button):hover,
|
|
147
|
+
footer .link:hover {
|
|
148
|
+
text-decoration: underline;
|
|
149
|
+
text-underline-offset: 2px;
|
|
150
|
+
}
|
|
151
|
+
|
|
140
152
|
.footer-columns {
|
|
141
153
|
columns: 1;
|
|
142
154
|
column-gap: 2rem;
|
|
@@ -36,6 +36,26 @@ describe("<HeroSection />", () => {
|
|
|
36
36
|
cy.get("img.hero-image").should("have.attr", "src", "/image.png");
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it("renders with left alignment by default", () => {
|
|
40
|
+
cy.mount(HeroSection);
|
|
41
|
+
cy.get(".hero-container").should("have.css", "text-align", "left");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("supports center alignment when configured", () => {
|
|
45
|
+
__setMockData({
|
|
46
|
+
frontmatter: {
|
|
47
|
+
hero: {
|
|
48
|
+
text: "Centered Hero",
|
|
49
|
+
align: "center",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
cy.mount(HeroSection);
|
|
55
|
+
cy.get(".hero-container").should("have.class", "center-align");
|
|
56
|
+
cy.get(".hero-container").should("have.css", "text-align", "center");
|
|
57
|
+
});
|
|
58
|
+
|
|
39
59
|
it("renders video when provided", () => {
|
|
40
60
|
__setMockData({
|
|
41
61
|
frontmatter: {
|
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<header v-if="
|
|
2
|
+
<header v-if="heroData" class="primary hero-gradient-bg">
|
|
3
3
|
<div
|
|
4
|
-
:class="`large-padding hero-container ${
|
|
4
|
+
:class="`large-padding hero-container ${heroData.image ? 'image' : ''} ${heroData.align === 'center' || heroData.align === 'center-align' ? 'center-align' : ''}`"
|
|
5
5
|
>
|
|
6
6
|
<iframe
|
|
7
|
-
v-if="
|
|
7
|
+
v-if="heroData.image?.src?.includes('youtube')"
|
|
8
8
|
class="hero-image large-elevate small-round"
|
|
9
9
|
style="grid-area: image; aspect-ratio: 16/9"
|
|
10
|
-
:src="
|
|
10
|
+
:src="heroData.image.src"
|
|
11
11
|
frameborder="0"
|
|
12
12
|
allowfullscreen
|
|
13
13
|
/>
|
|
14
14
|
<video
|
|
15
|
-
v-else-if="
|
|
15
|
+
v-else-if="heroData.video"
|
|
16
16
|
class="hero-image large-elevate small-round"
|
|
17
|
-
:src="withBase(
|
|
17
|
+
:src="withBase(heroData.video.src)"
|
|
18
18
|
style="grid-area: image; aspect-ratio: 16/9"
|
|
19
19
|
autoplay
|
|
20
20
|
muted
|
|
21
21
|
loop
|
|
22
22
|
/>
|
|
23
23
|
<img
|
|
24
|
-
v-else-if="
|
|
24
|
+
v-else-if="heroData.image"
|
|
25
25
|
class="hero-image large-elevate small-round"
|
|
26
26
|
style="grid-area: image"
|
|
27
|
-
:src="withBase(
|
|
27
|
+
:src="withBase(heroData.image.src)"
|
|
28
|
+
:alt="heroData.image.alt || heroData.text"
|
|
28
29
|
/>
|
|
29
|
-
<div class="title" style="grid-area: title
|
|
30
|
+
<div class="title" style="grid-area: title">
|
|
30
31
|
<h1 class="bold" style="font-size: clamp(2.25rem, 5vw, 3.3rem)">
|
|
31
|
-
{{
|
|
32
|
+
{{ heroData.text }}
|
|
32
33
|
</h1>
|
|
33
|
-
<p v-html="
|
|
34
|
+
<p v-if="heroData.tagline" v-html="heroData.tagline"></p>
|
|
34
35
|
</div>
|
|
35
|
-
<div
|
|
36
|
+
<div
|
|
37
|
+
v-if="heroData.actions && heroData.actions.length > 0"
|
|
38
|
+
style="grid-area: actions"
|
|
39
|
+
>
|
|
36
40
|
<a
|
|
37
|
-
v-for="(action, i) in
|
|
41
|
+
v-for="(action, i) in heroData.actions"
|
|
42
|
+
:key="i"
|
|
38
43
|
:href="withBase(action.link)"
|
|
39
44
|
:target="action.target"
|
|
40
45
|
:rel="action.rel"
|
|
@@ -57,35 +62,135 @@
|
|
|
57
62
|
"
|
|
58
63
|
>
|
|
59
64
|
<span>{{ action.text }}</span>
|
|
60
|
-
<i
|
|
65
|
+
<i
|
|
66
|
+
v-if="action.icon"
|
|
67
|
+
:class="
|
|
68
|
+
action.icon.startsWith('mdi') ? `mdi ${action.icon}` : action.icon
|
|
69
|
+
"
|
|
70
|
+
></i>
|
|
71
|
+
<i
|
|
72
|
+
v-else-if="action.theme !== 'alt' && action.icon !== false"
|
|
73
|
+
class="mdi mdi-arrow-right"
|
|
74
|
+
></i>
|
|
61
75
|
</a>
|
|
62
76
|
</div>
|
|
63
77
|
</div>
|
|
64
78
|
<img
|
|
65
|
-
v-if="
|
|
79
|
+
v-if="heroData.background"
|
|
66
80
|
class="background-image"
|
|
67
|
-
:src="withBase(
|
|
81
|
+
:src="withBase(heroData.background.src)"
|
|
82
|
+
:alt="heroData.background.alt || ''"
|
|
68
83
|
/>
|
|
69
84
|
</header>
|
|
70
85
|
</template>
|
|
71
86
|
|
|
72
87
|
<script setup>
|
|
88
|
+
import { computed } from "vue";
|
|
73
89
|
import { useData, withBase } from "vitepress";
|
|
74
90
|
import { trackEvent } from "../helpers";
|
|
75
|
-
|
|
91
|
+
|
|
92
|
+
const props = defineProps({
|
|
93
|
+
text: String,
|
|
94
|
+
title: String,
|
|
95
|
+
tagline: String,
|
|
96
|
+
actions: Array,
|
|
97
|
+
image: Object,
|
|
98
|
+
video: Object,
|
|
99
|
+
background: Object,
|
|
100
|
+
align: String,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const { frontmatter, params } = useData();
|
|
104
|
+
|
|
105
|
+
const heroData = computed(() => {
|
|
106
|
+
if (props.text || props.title || props.tagline) {
|
|
107
|
+
return {
|
|
108
|
+
text: props.text || props.title,
|
|
109
|
+
tagline: props.tagline,
|
|
110
|
+
actions: props.actions,
|
|
111
|
+
image: props.image,
|
|
112
|
+
video: props.video,
|
|
113
|
+
background: props.background,
|
|
114
|
+
align: props.align,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!frontmatter.value.hero || frontmatter.value.hero === false) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// If hero is an object with custom properties
|
|
123
|
+
if (typeof frontmatter.value.hero === "object") {
|
|
124
|
+
return {
|
|
125
|
+
...frontmatter.value.hero,
|
|
126
|
+
text:
|
|
127
|
+
frontmatter.value.hero.text ||
|
|
128
|
+
frontmatter.value.hero.title ||
|
|
129
|
+
frontmatter.value.title,
|
|
130
|
+
align: frontmatter.value.hero.align,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// If hero is true (boolean), provide intelligent layout defaults
|
|
135
|
+
if (frontmatter.value.hero === true) {
|
|
136
|
+
if (frontmatter.value.layout === "team") {
|
|
137
|
+
return {
|
|
138
|
+
text: frontmatter.value.title || "Our Team",
|
|
139
|
+
tagline:
|
|
140
|
+
frontmatter.value.tagline ||
|
|
141
|
+
"Meet the diverse team of geospatial experts, engineers, and visionaries driving EOX forward.",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (frontmatter.value.layout === "blog") {
|
|
145
|
+
const tag = params?.value?.tag;
|
|
146
|
+
const author = params?.value?.author;
|
|
147
|
+
let blogTitle = frontmatter.value.title || "EOX Blog";
|
|
148
|
+
if (tag) {
|
|
149
|
+
blogTitle = `Tag: ${tag}`;
|
|
150
|
+
} else if (author) {
|
|
151
|
+
blogTitle = `Author: ${author}`;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
text: blogTitle,
|
|
155
|
+
tagline:
|
|
156
|
+
frontmatter.value.tagline ||
|
|
157
|
+
"Insights, updates, and deep dives from our geospatial experts.",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (frontmatter.value.layout === "tags") {
|
|
161
|
+
return {
|
|
162
|
+
text: frontmatter.value.title || "Blog Tags",
|
|
163
|
+
tagline:
|
|
164
|
+
frontmatter.value.tagline ||
|
|
165
|
+
"Explore our blog posts by topics and technology pillars.",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
text: frontmatter.value.title || "",
|
|
170
|
+
tagline: frontmatter.value.tagline || frontmatter.value.description || "",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
});
|
|
76
176
|
</script>
|
|
77
177
|
|
|
78
|
-
<style>
|
|
178
|
+
<style scoped>
|
|
79
179
|
header {
|
|
180
|
+
position: relative;
|
|
80
181
|
margin-top: calc(var(--vp-nav-height) * -1);
|
|
81
|
-
padding-top:
|
|
82
|
-
padding-bottom:
|
|
83
|
-
|
|
84
|
-
|
|
182
|
+
padding-top: calc(var(--vp-nav-height) + 3.5rem) !important;
|
|
183
|
+
padding-bottom: 4.5rem !important;
|
|
184
|
+
padding-left: 0 !important;
|
|
185
|
+
padding-right: 0 !important;
|
|
186
|
+
min-height: 380px;
|
|
85
187
|
}
|
|
86
188
|
@media (max-width: 1024px) {
|
|
87
189
|
header {
|
|
88
|
-
padding:
|
|
190
|
+
padding-top: calc(var(--vp-nav-height) + 2rem) !important;
|
|
191
|
+
padding-bottom: 3rem !important;
|
|
192
|
+
padding-left: 0 !important;
|
|
193
|
+
padding-right: 0 !important;
|
|
89
194
|
}
|
|
90
195
|
header h1.large {
|
|
91
196
|
font-size: 3rem;
|
|
@@ -103,10 +208,12 @@ header > img.background-image {
|
|
|
103
208
|
border-radius: 0 !important;
|
|
104
209
|
}
|
|
105
210
|
header > .hero-container {
|
|
211
|
+
width: 100%;
|
|
106
212
|
max-width: 1200px;
|
|
107
213
|
max-height: 80%;
|
|
108
214
|
margin-left: auto;
|
|
109
215
|
margin-right: auto;
|
|
216
|
+
box-sizing: border-box;
|
|
110
217
|
display: grid;
|
|
111
218
|
grid-gap: 2rem;
|
|
112
219
|
grid-template-areas:
|
|
@@ -114,7 +221,7 @@ header > .hero-container {
|
|
|
114
221
|
"title"
|
|
115
222
|
"actions";
|
|
116
223
|
grid-auto-rows: min-content;
|
|
117
|
-
text-align:
|
|
224
|
+
text-align: left;
|
|
118
225
|
z-index: 1;
|
|
119
226
|
}
|
|
120
227
|
@media (min-width: 768px) {
|
|
@@ -122,7 +229,7 @@ header > .hero-container {
|
|
|
122
229
|
grid-template-areas:
|
|
123
230
|
"title"
|
|
124
231
|
"actions";
|
|
125
|
-
text-align:
|
|
232
|
+
text-align: left;
|
|
126
233
|
}
|
|
127
234
|
header > .hero-container.image {
|
|
128
235
|
grid-template-areas:
|
|
@@ -132,6 +239,14 @@ header > .hero-container {
|
|
|
132
239
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
133
240
|
}
|
|
134
241
|
}
|
|
242
|
+
header > .hero-container.center-align {
|
|
243
|
+
text-align: center;
|
|
244
|
+
}
|
|
245
|
+
@media (min-width: 768px) {
|
|
246
|
+
header > .hero-container.center-align {
|
|
247
|
+
text-align: center;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
135
250
|
header > .hero-container > .hero-image {
|
|
136
251
|
width: 100%;
|
|
137
252
|
object-fit: cover;
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
<script setup>
|
|
81
81
|
import { computed, ref } from "vue";
|
|
82
|
+
import { withBase } from "vitepress";
|
|
82
83
|
|
|
83
84
|
const props = defineProps({
|
|
84
85
|
images: {
|
|
@@ -90,14 +91,22 @@ const props = defineProps({
|
|
|
90
91
|
const container = ref(null);
|
|
91
92
|
const activeIndex = ref(0);
|
|
92
93
|
|
|
94
|
+
function resolveUrl(url) {
|
|
95
|
+
if (!url) return "";
|
|
96
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
97
|
+
const base = withBase("/");
|
|
98
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
99
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
const normalizedImages = computed(() => {
|
|
94
103
|
if (!props.images) return [];
|
|
95
104
|
return props.images.map((img) => {
|
|
96
105
|
if (typeof img === "string") {
|
|
97
|
-
return { src: img, alt: "", caption: "" };
|
|
106
|
+
return { src: resolveUrl(img), alt: "", caption: "" };
|
|
98
107
|
}
|
|
99
108
|
return {
|
|
100
|
-
src: img.src
|
|
109
|
+
src: resolveUrl(img.src),
|
|
101
110
|
alt: img.alt || "",
|
|
102
111
|
caption: img.caption || "",
|
|
103
112
|
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
<!-- Background Image (Original/Right) -->
|
|
11
11
|
<img
|
|
12
|
-
:src="
|
|
12
|
+
:src="resolvedCompare"
|
|
13
13
|
:alt="alt || 'Image comparison background'"
|
|
14
14
|
class="compare-img compare-bg"
|
|
15
15
|
/>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}"
|
|
23
23
|
>
|
|
24
24
|
<img
|
|
25
|
-
:src="
|
|
25
|
+
:src="resolvedSrc"
|
|
26
26
|
:alt="alt || 'Image comparison foreground'"
|
|
27
27
|
class="compare-img compare-fg"
|
|
28
28
|
/>
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
<!-- Standard Image Mode -->
|
|
62
62
|
<img
|
|
63
63
|
v-else
|
|
64
|
-
:src="
|
|
64
|
+
:src="resolvedSrc"
|
|
65
65
|
:alt="alt || 'Post image'"
|
|
66
66
|
:style="{ maxWidth: width || '100%' }"
|
|
67
67
|
class="standard-img"
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
</template>
|
|
79
79
|
|
|
80
80
|
<script setup>
|
|
81
|
-
import { ref } from "vue";
|
|
81
|
+
import { ref, computed } from "vue";
|
|
82
|
+
import { withBase } from "vitepress";
|
|
82
83
|
|
|
83
84
|
const props = defineProps({
|
|
84
85
|
src: {
|
|
@@ -103,6 +104,17 @@ const props = defineProps({
|
|
|
103
104
|
},
|
|
104
105
|
});
|
|
105
106
|
|
|
107
|
+
function resolveUrl(url) {
|
|
108
|
+
if (!url) return "";
|
|
109
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
110
|
+
const base = withBase("/");
|
|
111
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
112
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const resolvedSrc = computed(() => resolveUrl(props.src));
|
|
116
|
+
const resolvedCompare = computed(() => resolveUrl(props.compare));
|
|
117
|
+
|
|
106
118
|
const sliderValue = ref(50);
|
|
107
119
|
</script>
|
|
108
120
|
|
|
@@ -170,6 +170,7 @@
|
|
|
170
170
|
|
|
171
171
|
<script setup>
|
|
172
172
|
import { computed, ref, onMounted, onUnmounted } from "vue";
|
|
173
|
+
import { withBase } from "vitepress";
|
|
173
174
|
|
|
174
175
|
const props = defineProps({
|
|
175
176
|
images: {
|
|
@@ -192,6 +193,14 @@ const props = defineProps({
|
|
|
192
193
|
|
|
193
194
|
const lightboxIndex = ref(null);
|
|
194
195
|
|
|
196
|
+
function resolveUrl(url) {
|
|
197
|
+
if (!url) return "";
|
|
198
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
199
|
+
const base = withBase("/");
|
|
200
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
201
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
202
|
+
}
|
|
203
|
+
|
|
195
204
|
const layoutMode = computed(() => {
|
|
196
205
|
// If explicitly 'grid', or if we detect link in image objects, default to grid
|
|
197
206
|
if (props.layout === "grid") return "grid";
|
|
@@ -211,13 +220,23 @@ const normalizedImages = computed(() => {
|
|
|
211
220
|
if (Array.isArray(props.images)) {
|
|
212
221
|
return props.images.map((img) => {
|
|
213
222
|
if (typeof img === "string") {
|
|
214
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
src: resolveUrl(img),
|
|
225
|
+
alt: "",
|
|
226
|
+
caption: "",
|
|
227
|
+
link: "",
|
|
228
|
+
width: "",
|
|
229
|
+
};
|
|
215
230
|
}
|
|
216
231
|
return {
|
|
217
|
-
src: img.src
|
|
232
|
+
src: resolveUrl(img.src),
|
|
218
233
|
alt: img.alt || "",
|
|
219
234
|
caption: img.caption || "",
|
|
220
|
-
link: img.link
|
|
235
|
+
link: img.link
|
|
236
|
+
? img.link.startsWith("/")
|
|
237
|
+
? withBase(img.link)
|
|
238
|
+
: img.link
|
|
239
|
+
: "",
|
|
221
240
|
width: img.width || "",
|
|
222
241
|
};
|
|
223
242
|
});
|
|
@@ -227,7 +246,13 @@ const normalizedImages = computed(() => {
|
|
|
227
246
|
.split(",")
|
|
228
247
|
.map((img) => img.trim())
|
|
229
248
|
.filter(Boolean)
|
|
230
|
-
.map((src) => ({
|
|
249
|
+
.map((src) => ({
|
|
250
|
+
src: resolveUrl(src),
|
|
251
|
+
alt: "",
|
|
252
|
+
caption: "",
|
|
253
|
+
link: "",
|
|
254
|
+
width: "",
|
|
255
|
+
}));
|
|
231
256
|
}
|
|
232
257
|
return [];
|
|
233
258
|
});
|