@eox/pages-theme-eox 2.0.0-alpha.2 → 2.0.0-alpha.4
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 +1 -1
- package/src/components/BlogList.vue +35 -33
- package/src/components/BlogPost.vue +29 -23
- package/src/components/CTASection.vue +15 -3
- package/src/components/FeatureCard/Index.vue +79 -23
- package/src/components/FeatureCard/Poster.vue +23 -3
- package/src/components/FeaturesGallery.vue +31 -2
- package/src/components/Footer.vue +5 -2
- package/src/components/HeroSection.vue +6 -6
- package/src/components/ImageCarousel.vue +9 -2
- package/src/components/ImageElement.vue +14 -4
- package/src/components/ImageGallery.vue +27 -4
- package/src/components/LogoSection.vue +19 -2
- package/src/components/NavBar.vue +3 -3
- package/src/components/TagList.vue +16 -8
- package/src/components/TeamPage.vue +15 -11
- package/src/style.css +62 -2
- package/src/tagUtils.js +5 -5
- package/src/vitepressConfig.mjs +2 -2
package/package.json
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
<div class="blog-landing-header" role="banner">
|
|
5
5
|
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
|
|
6
6
|
<div>
|
|
7
|
-
<h1 class="blog-landing-title">
|
|
8
|
-
EOX Blog
|
|
9
|
-
</h1>
|
|
7
|
+
<h1 class="blog-landing-title">EOX Blog</h1>
|
|
10
8
|
<p class="blog-landing-subtitle">
|
|
11
9
|
Insights, updates, and deep dives from our geospatial experts.
|
|
12
10
|
</p>
|
|
@@ -14,10 +12,7 @@
|
|
|
14
12
|
</div>
|
|
15
13
|
|
|
16
14
|
<!-- Active Filter Banner -->
|
|
17
|
-
<div
|
|
18
|
-
v-if="activeTag || activeAuthor"
|
|
19
|
-
class="active-filter-banner"
|
|
20
|
-
>
|
|
15
|
+
<div v-if="activeTag || activeAuthor" class="active-filter-banner">
|
|
21
16
|
<span class="filter-label">Active filter:</span>
|
|
22
17
|
<span
|
|
23
18
|
v-if="activeTag"
|
|
@@ -32,10 +27,7 @@
|
|
|
32
27
|
>
|
|
33
28
|
Author: {{ getAuthorName(activeAuthor) }}
|
|
34
29
|
</span>
|
|
35
|
-
<button
|
|
36
|
-
@click="clearFilters"
|
|
37
|
-
class="clear-filter-btn"
|
|
38
|
-
>
|
|
30
|
+
<button @click="clearFilters" class="clear-filter-btn">
|
|
39
31
|
<i class="mdi mdi-close-circle"></i>
|
|
40
32
|
<span>Clear Filter</span>
|
|
41
33
|
</button>
|
|
@@ -52,14 +44,23 @@
|
|
|
52
44
|
:key="post.url"
|
|
53
45
|
:title="post.title"
|
|
54
46
|
:content="post.excerpt"
|
|
55
|
-
:image="
|
|
47
|
+
:image="
|
|
48
|
+
post.coverImage ? getCoverImageUrl(post.coverImage) : undefined
|
|
49
|
+
"
|
|
56
50
|
:date="post.date"
|
|
57
51
|
:authors="post.authors"
|
|
58
|
-
:chips="
|
|
52
|
+
:chips="
|
|
53
|
+
post.tags
|
|
54
|
+
? post.tags.map((t) => ({
|
|
55
|
+
text: t,
|
|
56
|
+
link: withBase(`/tag/${t}/`),
|
|
57
|
+
}))
|
|
58
|
+
: []
|
|
59
|
+
"
|
|
59
60
|
:link="{
|
|
60
61
|
text: 'Read more',
|
|
61
|
-
href: post.url,
|
|
62
|
-
target: '_self'
|
|
62
|
+
href: withBase(post.url),
|
|
63
|
+
target: '_self',
|
|
63
64
|
}"
|
|
64
65
|
variant="cover"
|
|
65
66
|
class="h-full bg-surface-container-low"
|
|
@@ -87,18 +88,18 @@
|
|
|
87
88
|
<!-- Pagination -->
|
|
88
89
|
<nav
|
|
89
90
|
v-if="totalPages > 1"
|
|
90
|
-
class="mt-16 flex justify-center items-center gap-2"
|
|
91
|
+
class="mt-16 flex justify-center items-center gap-1 sm:gap-2"
|
|
91
92
|
>
|
|
92
93
|
<button
|
|
93
94
|
@click="changePage(currentPage - 1)"
|
|
94
95
|
:disabled="currentPage === 1"
|
|
95
|
-
class="
|
|
96
|
+
class="min-w-[32px] sm:min-w-[40px] h-[32px] sm:h-[40px] flex items-center justify-center rounded-xl border border-slate-200 text-slate-600 hover:bg-slate-50 disabled:opacity-40 disabled:hover:bg-transparent disabled:cursor-not-allowed transition-colors"
|
|
96
97
|
>
|
|
97
98
|
<span class="sr-only">Previous</span>
|
|
98
99
|
<i class="mdi mdi-chevron-left text-lg"></i>
|
|
99
100
|
</button>
|
|
100
101
|
|
|
101
|
-
<div class="flex items-center gap-1.5">
|
|
102
|
+
<div class="flex items-center gap-1 sm:gap-1.5">
|
|
102
103
|
<button
|
|
103
104
|
v-for="page in visiblePages"
|
|
104
105
|
:key="page"
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
? 'bg-primary text-white border-primary shadow-sm'
|
|
109
110
|
: 'border-slate-200 text-slate-600 hover:bg-slate-50',
|
|
110
111
|
]"
|
|
111
|
-
class="min-w-[40px] h-[40px] px-3 font-semibold text-sm rounded-xl border flex items-center justify-center transition-all duration-150"
|
|
112
|
+
class="min-w-[32px] sm:min-w-[40px] h-[32px] sm:h-[40px] px-2 sm:px-3 font-semibold text-sm rounded-xl border flex items-center justify-center transition-all duration-150"
|
|
112
113
|
>
|
|
113
114
|
{{ page }}
|
|
114
115
|
</button>
|
|
@@ -117,7 +118,7 @@
|
|
|
117
118
|
<button
|
|
118
119
|
@click="changePage(currentPage + 1)"
|
|
119
120
|
:disabled="currentPage === totalPages"
|
|
120
|
-
class="
|
|
121
|
+
class="min-w-[32px] sm:min-w-[40px] h-[32px] sm:h-[40px] flex items-center justify-center rounded-xl border border-slate-200 text-slate-600 hover:bg-slate-50 disabled:opacity-40 disabled:hover:bg-transparent disabled:cursor-not-allowed transition-colors"
|
|
121
122
|
>
|
|
122
123
|
<span class="sr-only">Next</span>
|
|
123
124
|
<i class="mdi mdi-chevron-right text-lg"></i>
|
|
@@ -129,9 +130,7 @@
|
|
|
129
130
|
<aside class="blog-sidebar-col">
|
|
130
131
|
<!-- Search widget -->
|
|
131
132
|
<div class="sidebar-widget">
|
|
132
|
-
<h3 class="sidebar-title">
|
|
133
|
-
Search
|
|
134
|
-
</h3>
|
|
133
|
+
<h3 class="sidebar-title">Search</h3>
|
|
135
134
|
<div class="search-container">
|
|
136
135
|
<VPNavBarSearch class="search" />
|
|
137
136
|
</div>
|
|
@@ -140,10 +139,8 @@
|
|
|
140
139
|
<!-- Hot Tags -->
|
|
141
140
|
<div class="sidebar-widget">
|
|
142
141
|
<div class="sidebar-title-container">
|
|
143
|
-
<h3 class="sidebar-title-inline">
|
|
144
|
-
|
|
145
|
-
</h3>
|
|
146
|
-
<a href="/tag" class="see-all-link">
|
|
142
|
+
<h3 class="sidebar-title-inline">Hot tags</h3>
|
|
143
|
+
<a :href="withBase('/tag')" class="see-all-link">
|
|
147
144
|
<span>see all</span>
|
|
148
145
|
<i class="mdi mdi-arrow-right"></i>
|
|
149
146
|
</a>
|
|
@@ -152,7 +149,7 @@
|
|
|
152
149
|
<a
|
|
153
150
|
v-for="tag in calculatedHotTags"
|
|
154
151
|
:key="tag.name"
|
|
155
|
-
:href="`/tag/${tag.name}/`"
|
|
152
|
+
:href="withBase(`/tag/${tag.name}/`)"
|
|
156
153
|
:style="getTagStyle(tag.name)"
|
|
157
154
|
class="sidebar-tag"
|
|
158
155
|
>
|
|
@@ -167,7 +164,7 @@
|
|
|
167
164
|
|
|
168
165
|
<script setup>
|
|
169
166
|
import { ref, computed, onMounted, watch } from "vue";
|
|
170
|
-
import { useRouter, useData } from "vitepress";
|
|
167
|
+
import { useRouter, useData, withBase } from "vitepress";
|
|
171
168
|
import { data as posts } from "../posts.data.js";
|
|
172
169
|
import { data as authorsList } from "../authors.data.js";
|
|
173
170
|
import { getTagColor, getTagStyle } from "../tagUtils.js";
|
|
@@ -208,10 +205,13 @@ const postsPerPage = 9;
|
|
|
208
205
|
// Form cover image URL correctly (prefixing filename with /images/)
|
|
209
206
|
function getCoverImageUrl(coverImage) {
|
|
210
207
|
if (!coverImage) return "";
|
|
211
|
-
if (coverImage.startsWith("
|
|
208
|
+
if (coverImage.startsWith("http") || coverImage.startsWith("//")) {
|
|
212
209
|
return coverImage;
|
|
213
210
|
}
|
|
214
|
-
|
|
211
|
+
const path = coverImage.startsWith("/")
|
|
212
|
+
? coverImage
|
|
213
|
+
: `/images/${coverImage}`;
|
|
214
|
+
return withBase(path);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
const tagFromParam = computed(() => {
|
|
@@ -522,7 +522,9 @@ function clearFilters() {
|
|
|
522
522
|
border-radius: 4px !important;
|
|
523
523
|
text-decoration: none !important;
|
|
524
524
|
line-height: 1.2 !important;
|
|
525
|
-
transition:
|
|
525
|
+
transition:
|
|
526
|
+
opacity 0.2s ease,
|
|
527
|
+
transform 0.1s ease !important;
|
|
526
528
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
|
|
527
529
|
}
|
|
528
530
|
|
|
@@ -584,7 +586,7 @@ function clearFilters() {
|
|
|
584
586
|
padding: 0 !important;
|
|
585
587
|
margin: 0 !important;
|
|
586
588
|
margin-left: 0.5rem !important;
|
|
587
|
-
|
|
589
|
+
|
|
588
590
|
display: inline-flex !important;
|
|
589
591
|
align-items: center !important;
|
|
590
592
|
gap: 0.375rem !important;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
{{ formatDate(frontmatter.date) }}
|
|
31
31
|
</time>
|
|
32
32
|
</span>
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
<!-- Author Span -->
|
|
35
35
|
<span v-if="authors && authors.length" class="metadata-item">
|
|
36
36
|
<i class="mdi mdi-account-outline metadata-icon"></i>
|
|
@@ -41,12 +41,14 @@
|
|
|
41
41
|
class="author-item"
|
|
42
42
|
>
|
|
43
43
|
<a
|
|
44
|
-
:href="`/author/${authorId}/`"
|
|
44
|
+
:href="withBase(`/author/${authorId}/`)"
|
|
45
45
|
class="author-link-active"
|
|
46
46
|
>
|
|
47
47
|
{{ getAuthorName(authorId) }}
|
|
48
48
|
</a>
|
|
49
|
-
<span v-if="idx < authors.length - 1" class="author-separator"
|
|
49
|
+
<span v-if="idx < authors.length - 1" class="author-separator"
|
|
50
|
+
>,</span
|
|
51
|
+
>
|
|
50
52
|
</span>
|
|
51
53
|
</span>
|
|
52
54
|
</div>
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
<a
|
|
60
62
|
v-for="tag in frontmatter.tags"
|
|
61
63
|
:key="tag"
|
|
62
|
-
:href="`/tag/${tag}/`"
|
|
64
|
+
:href="withBase(`/tag/${tag}/`)"
|
|
63
65
|
:style="getTagStyle(tag)"
|
|
64
66
|
class="post-tag"
|
|
65
67
|
>
|
|
@@ -77,23 +79,18 @@
|
|
|
77
79
|
<!-- Sidebar Column -->
|
|
78
80
|
<div class="blog-sidebar-col">
|
|
79
81
|
<!-- Table of Contents (On This Page) -->
|
|
80
|
-
<div
|
|
81
|
-
v-if="headers && headers.length"
|
|
82
|
-
class="toc-container"
|
|
83
|
-
>
|
|
82
|
+
<div v-if="headers && headers.length" class="toc-container">
|
|
84
83
|
<VPDocAsideOutline />
|
|
85
84
|
</div>
|
|
86
85
|
|
|
87
86
|
<!-- Hot Tags -->
|
|
88
87
|
<div class="sidebar-widget">
|
|
89
|
-
<h3 class="sidebar-title">
|
|
90
|
-
Hot tags
|
|
91
|
-
</h3>
|
|
88
|
+
<h3 class="sidebar-title">Hot tags</h3>
|
|
92
89
|
<div class="sidebar-tags-container">
|
|
93
90
|
<a
|
|
94
91
|
v-for="tag in calculatedHotTags"
|
|
95
92
|
:key="tag.name"
|
|
96
|
-
:href="`/tag/${tag.name}/`"
|
|
93
|
+
:href="withBase(`/tag/${tag.name}/`)"
|
|
97
94
|
:style="getTagStyle(tag.name)"
|
|
98
95
|
class="sidebar-tag"
|
|
99
96
|
>
|
|
@@ -104,9 +101,7 @@
|
|
|
104
101
|
|
|
105
102
|
<!-- Recent Posts -->
|
|
106
103
|
<div class="sidebar-widget">
|
|
107
|
-
<h3 class="sidebar-title">
|
|
108
|
-
Recent posts
|
|
109
|
-
</h3>
|
|
104
|
+
<h3 class="sidebar-title">Recent posts</h3>
|
|
110
105
|
<div class="space-y-4">
|
|
111
106
|
<div
|
|
112
107
|
v-for="post in calculatedRecentPosts"
|
|
@@ -114,7 +109,7 @@
|
|
|
114
109
|
class="flex flex-col gap-1 border-b border-slate-100 last:border-none pb-3 last:pb-0"
|
|
115
110
|
>
|
|
116
111
|
<a
|
|
117
|
-
:href="post.url"
|
|
112
|
+
:href="withBase(post.url)"
|
|
118
113
|
class="text-sm font-semibold text-slate-900 hover:text-primary leading-snug transition-colors line-clamp-2"
|
|
119
114
|
>
|
|
120
115
|
{{ post.title }}
|
|
@@ -131,7 +126,7 @@
|
|
|
131
126
|
</template>
|
|
132
127
|
|
|
133
128
|
<script setup>
|
|
134
|
-
import { useData } from "vitepress";
|
|
129
|
+
import { useData, withBase } from "vitepress";
|
|
135
130
|
import { computed } from "vue";
|
|
136
131
|
import { data as posts } from "../posts.data.js";
|
|
137
132
|
import { data as authorsList } from "../authors.data.js";
|
|
@@ -153,10 +148,13 @@ const headers = computed(() => {
|
|
|
153
148
|
// Form cover image URL correctly (prefixing filename with /images/)
|
|
154
149
|
function getCoverImageUrl(coverImage) {
|
|
155
150
|
if (!coverImage) return "";
|
|
156
|
-
if (coverImage.startsWith("
|
|
151
|
+
if (coverImage.startsWith("http") || coverImage.startsWith("//")) {
|
|
157
152
|
return coverImage;
|
|
158
153
|
}
|
|
159
|
-
|
|
154
|
+
const path = coverImage.startsWith("/")
|
|
155
|
+
? coverImage
|
|
156
|
+
: `/images/${coverImage}`;
|
|
157
|
+
return withBase(path);
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
// Format publication date
|
|
@@ -320,7 +318,9 @@ const calculatedHotTags = computed(() => {
|
|
|
320
318
|
align-items: center;
|
|
321
319
|
justify-content: center;
|
|
322
320
|
height: 100%;
|
|
323
|
-
transform: translateY(
|
|
321
|
+
transform: translateY(
|
|
322
|
+
0.5px
|
|
323
|
+
); /* Micro-adjustment for perfect vertical alignment */
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
.metadata-text {
|
|
@@ -343,7 +343,9 @@ const calculatedHotTags = computed(() => {
|
|
|
343
343
|
font-weight: 600;
|
|
344
344
|
color: var(--vp-c-brand, #004170) !important;
|
|
345
345
|
text-decoration: none;
|
|
346
|
-
transition:
|
|
346
|
+
transition:
|
|
347
|
+
color 0.15s ease,
|
|
348
|
+
text-decoration 0.15s ease;
|
|
347
349
|
line-height: 1.2;
|
|
348
350
|
}
|
|
349
351
|
|
|
@@ -391,7 +393,9 @@ const calculatedHotTags = computed(() => {
|
|
|
391
393
|
border-radius: 4px !important;
|
|
392
394
|
text-decoration: none !important;
|
|
393
395
|
line-height: 1.2 !important;
|
|
394
|
-
transition:
|
|
396
|
+
transition:
|
|
397
|
+
opacity 0.2s ease,
|
|
398
|
+
transform 0.1s ease !important;
|
|
395
399
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
|
|
396
400
|
}
|
|
397
401
|
|
|
@@ -416,7 +420,9 @@ const calculatedHotTags = computed(() => {
|
|
|
416
420
|
border-radius: 4px !important;
|
|
417
421
|
text-decoration: none !important;
|
|
418
422
|
line-height: 1.2 !important;
|
|
419
|
-
transition:
|
|
423
|
+
transition:
|
|
424
|
+
opacity 0.2s ease,
|
|
425
|
+
transform 0.1s ease !important;
|
|
420
426
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
|
|
421
427
|
}
|
|
422
428
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
v-if="primaryButton"
|
|
19
19
|
class="button extra small-margin responsive-mobile"
|
|
20
20
|
:class="dark !== undefined ? 'surface' : 'primary'"
|
|
21
|
-
:href="
|
|
21
|
+
:href="resolvedPrimaryLink"
|
|
22
22
|
@click="
|
|
23
23
|
trackEvent(['CTA', 'Click', 'CTA Section Primary', primaryButton])
|
|
24
24
|
"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<a
|
|
28
28
|
v-if="secondaryButton"
|
|
29
29
|
class="button extra small-margin secondary responsive-mobile"
|
|
30
|
-
:href="
|
|
30
|
+
:href="resolvedSecondaryLink"
|
|
31
31
|
@click="
|
|
32
32
|
trackEvent(['CTA', 'Click', 'CTA Section Secondary', secondaryButton])
|
|
33
33
|
"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
v-if="altButton"
|
|
38
38
|
class="button border extra small-margin responsive-mobile"
|
|
39
39
|
:class="dark !== undefined ? 'white-text' : ''"
|
|
40
|
-
:href="
|
|
40
|
+
:href="resolvedAltLink"
|
|
41
41
|
@click="trackEvent(['CTA', 'Click', 'CTA Section Alt', altButton])"
|
|
42
42
|
>{{ altButton }}</a
|
|
43
43
|
>
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
</template>
|
|
50
50
|
|
|
51
51
|
<script setup>
|
|
52
|
+
import { computed } from "vue";
|
|
53
|
+
import { withBase } from "vitepress";
|
|
52
54
|
import { trackEvent } from "../helpers";
|
|
53
55
|
const props = defineProps([
|
|
54
56
|
"dark",
|
|
@@ -61,6 +63,16 @@ const props = defineProps([
|
|
|
61
63
|
"tagline",
|
|
62
64
|
"title",
|
|
63
65
|
]);
|
|
66
|
+
|
|
67
|
+
function resolveLink(link) {
|
|
68
|
+
if (!link) return "";
|
|
69
|
+
if (link.startsWith("http") || link.startsWith("//")) return link;
|
|
70
|
+
return link.startsWith("/") ? withBase(link) : withBase("/" + link);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const resolvedPrimaryLink = computed(() => resolveLink(props.primaryLink));
|
|
74
|
+
const resolvedSecondaryLink = computed(() => resolveLink(props.secondaryLink));
|
|
75
|
+
const resolvedAltLink = computed(() => resolveLink(props.altLink));
|
|
64
76
|
</script>
|
|
65
77
|
|
|
66
78
|
<style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { withBase } from "vitepress";
|
|
3
4
|
import { data as authorsList } from "../../authors.data.js";
|
|
4
5
|
import { getTagColor } from "../../tagUtils.js";
|
|
5
6
|
|
|
@@ -53,6 +54,20 @@ const props = defineProps({
|
|
|
53
54
|
},
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
function resolveUrl(url) {
|
|
58
|
+
if (!url) return "";
|
|
59
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
60
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const resolvedImage = computed(() => resolveUrl(props.image));
|
|
64
|
+
const resolvedLinkHref = computed(() =>
|
|
65
|
+
props.link?.href ? resolveUrl(props.link.href) : undefined,
|
|
66
|
+
);
|
|
67
|
+
const resolvedSecondaryLinkHref = computed(() =>
|
|
68
|
+
props.secondaryLink?.href ? resolveUrl(props.secondaryLink.href) : undefined,
|
|
69
|
+
);
|
|
70
|
+
|
|
56
71
|
const iconStyle = {
|
|
57
72
|
width: (props.icon?.width ?? 40) + "px",
|
|
58
73
|
height: (props.icon?.height ?? 40) + "px",
|
|
@@ -60,7 +75,10 @@ const iconStyle = {
|
|
|
60
75
|
|
|
61
76
|
// Check if we should render a full-width cover image
|
|
62
77
|
const isCoverLayout = computed(() => {
|
|
63
|
-
return
|
|
78
|
+
return (
|
|
79
|
+
props.image &&
|
|
80
|
+
(props.variant === "cover" || props.date || props.authors.length)
|
|
81
|
+
);
|
|
64
82
|
});
|
|
65
83
|
|
|
66
84
|
// Check if the card itself is fully clickable (has a link but NO link text)
|
|
@@ -71,7 +89,7 @@ const isClickableCard = computed(() => {
|
|
|
71
89
|
function getAuthorName(authorId) {
|
|
72
90
|
if (!authorId) return "EOX Experts";
|
|
73
91
|
const author = authorsList.find(
|
|
74
|
-
(a) => a.id.toLowerCase().trim() === authorId.toLowerCase().trim()
|
|
92
|
+
(a) => a.id.toLowerCase().trim() === authorId.toLowerCase().trim(),
|
|
75
93
|
);
|
|
76
94
|
return author ? author.name : authorId;
|
|
77
95
|
}
|
|
@@ -82,31 +100,31 @@ function getTagStyle(tag) {
|
|
|
82
100
|
return {
|
|
83
101
|
backgroundColor: "rgba(255, 94, 94, 0.15)",
|
|
84
102
|
border: "1px solid rgb(255, 94, 94)",
|
|
85
|
-
color: "#d32f2f"
|
|
103
|
+
color: "#d32f2f",
|
|
86
104
|
};
|
|
87
105
|
} else if (color.startsWith("rgb(0, 177, 176)")) {
|
|
88
106
|
return {
|
|
89
107
|
backgroundColor: "rgba(0, 177, 176, 0.15)",
|
|
90
108
|
border: "1px solid rgb(0, 177, 176)",
|
|
91
|
-
color: "#00796b"
|
|
109
|
+
color: "#00796b",
|
|
92
110
|
};
|
|
93
111
|
} else if (color.startsWith("rgb(219, 173, 66)")) {
|
|
94
112
|
return {
|
|
95
113
|
backgroundColor: "rgba(219, 173, 66, 0.15)",
|
|
96
114
|
border: "1px solid rgb(219, 173, 66)",
|
|
97
|
-
color: "#f57c00"
|
|
115
|
+
color: "#f57c00",
|
|
98
116
|
};
|
|
99
117
|
} else if (color.startsWith("rgb(218, 71, 228)")) {
|
|
100
118
|
return {
|
|
101
119
|
backgroundColor: "rgba(218, 71, 228, 0.15)",
|
|
102
120
|
border: "1px solid rgb(218, 71, 228)",
|
|
103
|
-
color: "#c2185b"
|
|
121
|
+
color: "#c2185b",
|
|
104
122
|
};
|
|
105
123
|
}
|
|
106
124
|
return {
|
|
107
125
|
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
108
126
|
border: "1px solid rgba(0, 0, 0, 0.1)",
|
|
109
|
-
color: "#475569"
|
|
127
|
+
color: "#475569",
|
|
110
128
|
};
|
|
111
129
|
}
|
|
112
130
|
|
|
@@ -126,12 +144,17 @@ function formatDate(dateString) {
|
|
|
126
144
|
class="vertical large-padding no-margin theme-feature-card"
|
|
127
145
|
:class="[
|
|
128
146
|
isClickableCard ? 'small-elevate clickable-card' : 'no-elevate',
|
|
129
|
-
isCoverLayout ? 'has-cover-image' : ''
|
|
147
|
+
isCoverLayout ? 'has-cover-image' : '',
|
|
130
148
|
]"
|
|
131
149
|
>
|
|
132
150
|
<!-- Cover Image for cover/blog/rich cards -->
|
|
133
|
-
<a v-if="isCoverLayout" :href="
|
|
134
|
-
<img
|
|
151
|
+
<a v-if="isCoverLayout" :href="resolvedLinkHref" class="card-cover-link">
|
|
152
|
+
<img
|
|
153
|
+
:src="resolvedImage"
|
|
154
|
+
alt=""
|
|
155
|
+
class="card-cover-image"
|
|
156
|
+
loading="lazy"
|
|
157
|
+
/>
|
|
135
158
|
</a>
|
|
136
159
|
|
|
137
160
|
<div class="card-content-wrapper">
|
|
@@ -146,7 +169,12 @@ function formatDate(dateString) {
|
|
|
146
169
|
class="icon"
|
|
147
170
|
></div>
|
|
148
171
|
<!-- Render small icon image only if NOT rendering a full cover image -->
|
|
149
|
-
<img
|
|
172
|
+
<img
|
|
173
|
+
v-else-if="image && !isCoverLayout"
|
|
174
|
+
:src="resolvedImage"
|
|
175
|
+
alt=""
|
|
176
|
+
class="icon"
|
|
177
|
+
/>
|
|
150
178
|
|
|
151
179
|
<!-- Metadata & Tags directly under place/author -->
|
|
152
180
|
<div v-if="date || (authors && authors.length)" class="metadata-wrapper">
|
|
@@ -159,18 +187,33 @@ function formatDate(dateString) {
|
|
|
159
187
|
<span class="author-icon">
|
|
160
188
|
<i class="mdi mdi-account-outline"></i>
|
|
161
189
|
</span>
|
|
162
|
-
<span
|
|
163
|
-
|
|
190
|
+
<span
|
|
191
|
+
v-for="(authorId, idx) in authors"
|
|
192
|
+
:key="authorId"
|
|
193
|
+
class="author-item"
|
|
194
|
+
>
|
|
195
|
+
<a :href="withBase(`/author/${authorId}/`)" class="author-link">{{
|
|
196
|
+
getAuthorName(authorId)
|
|
197
|
+
}}</a
|
|
198
|
+
><span v-if="idx < authors.length - 1" class="author-separator"
|
|
199
|
+
>,</span
|
|
200
|
+
>
|
|
164
201
|
</span>
|
|
165
202
|
</span>
|
|
166
203
|
</div>
|
|
167
204
|
|
|
168
205
|
<!-- Tags directly under metadata if chips are provided and this is a rich card -->
|
|
169
206
|
<div v-if="chips && chips.length" class="blog-tags-container">
|
|
170
|
-
<a
|
|
171
|
-
v-for="chip in chips"
|
|
207
|
+
<a
|
|
208
|
+
v-for="chip in chips"
|
|
172
209
|
:key="chip.text"
|
|
173
|
-
:href="
|
|
210
|
+
:href="
|
|
211
|
+
chip.link
|
|
212
|
+
? resolveUrl(chip.link)
|
|
213
|
+
: chip.href
|
|
214
|
+
? resolveUrl(chip.href)
|
|
215
|
+
: withBase(`/tag/${chip.text}/`)
|
|
216
|
+
"
|
|
174
217
|
:style="getTagStyle(chip.text)"
|
|
175
218
|
class="tag-link"
|
|
176
219
|
>
|
|
@@ -180,7 +223,7 @@ function formatDate(dateString) {
|
|
|
180
223
|
</div>
|
|
181
224
|
|
|
182
225
|
<h5 class="small card-title-text">
|
|
183
|
-
<a v-if="
|
|
226
|
+
<a v-if="resolvedLinkHref" :href="resolvedLinkHref">{{ title }}</a>
|
|
184
227
|
<span v-else>{{ title }}</span>
|
|
185
228
|
</h5>
|
|
186
229
|
|
|
@@ -193,7 +236,13 @@ function formatDate(dateString) {
|
|
|
193
236
|
<template v-for="chip in chips" :key="chip.text">
|
|
194
237
|
<a
|
|
195
238
|
v-if="chip.link || chip.href"
|
|
196
|
-
:href="
|
|
239
|
+
:href="
|
|
240
|
+
chip.link
|
|
241
|
+
? resolveUrl(chip.link)
|
|
242
|
+
: chip.href
|
|
243
|
+
? resolveUrl(chip.href)
|
|
244
|
+
: undefined
|
|
245
|
+
"
|
|
197
246
|
:style="getTagStyle(chip.text)"
|
|
198
247
|
class="chip small-chip border clickable-chip"
|
|
199
248
|
:class="chip.class"
|
|
@@ -211,7 +260,10 @@ function formatDate(dateString) {
|
|
|
211
260
|
</template>
|
|
212
261
|
</div>
|
|
213
262
|
|
|
214
|
-
<p
|
|
263
|
+
<p
|
|
264
|
+
v-if="metadata && metadata.length && !date && !authors.length"
|
|
265
|
+
class="small-text"
|
|
266
|
+
>
|
|
215
267
|
<span
|
|
216
268
|
v-for="(item, index) in metadata"
|
|
217
269
|
:key="index"
|
|
@@ -240,7 +292,7 @@ function formatDate(dateString) {
|
|
|
240
292
|
>
|
|
241
293
|
<a
|
|
242
294
|
v-if="link && !!link.text"
|
|
243
|
-
:href="
|
|
295
|
+
:href="resolvedLinkHref"
|
|
244
296
|
:target="link.target ?? '_blank'"
|
|
245
297
|
class="button transparent bold primary-text no-padding"
|
|
246
298
|
>
|
|
@@ -251,7 +303,7 @@ function formatDate(dateString) {
|
|
|
251
303
|
</a>
|
|
252
304
|
<a
|
|
253
305
|
v-if="secondaryLink"
|
|
254
|
-
:href="
|
|
306
|
+
:href="resolvedSecondaryLinkHref"
|
|
255
307
|
:target="secondaryLink.target ?? '_blank'"
|
|
256
308
|
class="button transparent primary-text no-padding secondary-link"
|
|
257
309
|
>
|
|
@@ -295,13 +347,17 @@ function formatDate(dateString) {
|
|
|
295
347
|
/* Clickable Cards Hover System */
|
|
296
348
|
.theme-feature-card.clickable-card {
|
|
297
349
|
cursor: pointer;
|
|
298
|
-
transition:
|
|
350
|
+
transition:
|
|
351
|
+
transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
|
|
352
|
+
box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
|
|
299
353
|
will-change: transform, box-shadow;
|
|
300
354
|
}
|
|
301
355
|
|
|
302
356
|
.theme-feature-card.clickable-card:hover {
|
|
303
357
|
transform: translateY(-4px) !important;
|
|
304
|
-
box-shadow:
|
|
358
|
+
box-shadow:
|
|
359
|
+
0 12px 28px rgba(0, 0, 0, 0.12),
|
|
360
|
+
0 4px 8px rgba(0, 0, 0, 0.04) !important;
|
|
305
361
|
}
|
|
306
362
|
|
|
307
363
|
.small-chip {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { withBase } from "vitepress";
|
|
3
4
|
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
title: String,
|
|
@@ -41,6 +42,20 @@ const props = defineProps({
|
|
|
41
42
|
},
|
|
42
43
|
});
|
|
43
44
|
|
|
45
|
+
function resolveUrl(url) {
|
|
46
|
+
if (!url) return "";
|
|
47
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
48
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const resolvedImage = computed(() => resolveUrl(props.image));
|
|
52
|
+
const resolvedLinkHref = computed(() =>
|
|
53
|
+
props.link?.href ? resolveUrl(props.link.href) : undefined,
|
|
54
|
+
);
|
|
55
|
+
const resolvedSecondaryLinkHref = computed(() =>
|
|
56
|
+
props.secondaryLink?.href ? resolveUrl(props.secondaryLink.href) : undefined,
|
|
57
|
+
);
|
|
58
|
+
|
|
44
59
|
const cardStyle = computed(() => ({
|
|
45
60
|
minHeight: props.minHeight,
|
|
46
61
|
}));
|
|
@@ -52,7 +67,12 @@ const cardStyle = computed(() => ({
|
|
|
52
67
|
:class="link && !link.text ? 'small-elevate' : 'no-elevate'"
|
|
53
68
|
:style="cardStyle"
|
|
54
69
|
>
|
|
55
|
-
<img
|
|
70
|
+
<img
|
|
71
|
+
v-if="image"
|
|
72
|
+
:src="resolvedImage"
|
|
73
|
+
alt=""
|
|
74
|
+
class="image-overlay-card-image"
|
|
75
|
+
/>
|
|
56
76
|
<div class="image-overlay-card-overlay"></div>
|
|
57
77
|
<div class="image-overlay-card-content padding">
|
|
58
78
|
<div style="padding-top: 3rem">
|
|
@@ -103,7 +123,7 @@ const cardStyle = computed(() => ({
|
|
|
103
123
|
<a
|
|
104
124
|
v-if="link && link.text"
|
|
105
125
|
class="button small white black-text"
|
|
106
|
-
:href="
|
|
126
|
+
:href="resolvedLinkHref"
|
|
107
127
|
:target="link.target ?? '_blank'"
|
|
108
128
|
>
|
|
109
129
|
{{ link.text }}
|
|
@@ -111,7 +131,7 @@ const cardStyle = computed(() => ({
|
|
|
111
131
|
<a
|
|
112
132
|
v-if="secondaryLink"
|
|
113
133
|
class="button small border white-text secondary-link"
|
|
114
|
-
:href="
|
|
134
|
+
:href="resolvedSecondaryLinkHref"
|
|
115
135
|
:target="secondaryLink.target ?? '_blank'"
|
|
116
136
|
>
|
|
117
137
|
{{ secondaryLink.text }}
|
|
@@ -63,9 +63,38 @@ const resolveCardComponent = (variant) => {
|
|
|
63
63
|
|
|
64
64
|
const { page, site } = useData();
|
|
65
65
|
|
|
66
|
+
function normalizeFeature(feature) {
|
|
67
|
+
return {
|
|
68
|
+
...feature,
|
|
69
|
+
image:
|
|
70
|
+
feature.image && feature.image.startsWith("/")
|
|
71
|
+
? withBase(feature.image)
|
|
72
|
+
: feature.image,
|
|
73
|
+
link: feature.link
|
|
74
|
+
? {
|
|
75
|
+
...feature.link,
|
|
76
|
+
href:
|
|
77
|
+
feature.link.href && feature.link.href.startsWith("/")
|
|
78
|
+
? withBase(feature.link.href)
|
|
79
|
+
: feature.link.href,
|
|
80
|
+
}
|
|
81
|
+
: undefined,
|
|
82
|
+
secondaryLink: feature.secondaryLink
|
|
83
|
+
? {
|
|
84
|
+
...feature.secondaryLink,
|
|
85
|
+
href:
|
|
86
|
+
feature.secondaryLink.href &&
|
|
87
|
+
feature.secondaryLink.href.startsWith("/")
|
|
88
|
+
? withBase(feature.secondaryLink.href)
|
|
89
|
+
: feature.secondaryLink.href,
|
|
90
|
+
}
|
|
91
|
+
: undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
const featuresExcerpts =
|
|
67
96
|
cards && cards.length
|
|
68
|
-
? cards
|
|
97
|
+
? cards.map(normalizeFeature)
|
|
69
98
|
: features
|
|
70
99
|
.map((f) => {
|
|
71
100
|
if (import.meta.env.SSR) {
|
|
@@ -89,7 +118,7 @@ const featuresExcerpts =
|
|
|
89
118
|
}
|
|
90
119
|
: undefined,
|
|
91
120
|
link: {
|
|
92
|
-
href: f.url,
|
|
121
|
+
href: withBase(f.url),
|
|
93
122
|
text: "Read more",
|
|
94
123
|
target: "_self",
|
|
95
124
|
},
|
|
@@ -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)"
|
|
@@ -89,7 +92,7 @@
|
|
|
89
92
|
>
|
|
90
93
|
</p>
|
|
91
94
|
<p v-if="theme.theme.brandConfig?.analytics">
|
|
92
|
-
<a href="/cookie-settings" class="link">{{
|
|
95
|
+
<a :href="withBase('/cookie-settings')" class="link">{{
|
|
93
96
|
t("Cookie settings", theme.i18n)
|
|
94
97
|
}}</a>
|
|
95
98
|
</p>
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
style="grid-area: image"
|
|
27
27
|
:src="withBase(frontmatter.hero.image.src)"
|
|
28
28
|
/>
|
|
29
|
-
<div class="title" style="grid-area: title
|
|
29
|
+
<div class="title" style="grid-area: title">
|
|
30
30
|
<h1 class="bold" style="font-size: clamp(2.25rem, 5vw, 3.3rem)">
|
|
31
31
|
{{ frontmatter.hero.text }}
|
|
32
32
|
</h1>
|
|
@@ -77,15 +77,15 @@ const { frontmatter } = useData();
|
|
|
77
77
|
|
|
78
78
|
<style scoped>
|
|
79
79
|
header {
|
|
80
|
+
position: relative;
|
|
80
81
|
margin-top: calc(var(--vp-nav-height) * -1);
|
|
81
|
-
padding-top:
|
|
82
|
-
padding-bottom:
|
|
83
|
-
height:
|
|
84
|
-
max-height: 1000px;
|
|
82
|
+
padding-top: calc(var(--vp-nav-height) + 3.5rem) !important;
|
|
83
|
+
padding-bottom: 4.5rem !important;
|
|
84
|
+
min-height: 380px;
|
|
85
85
|
}
|
|
86
86
|
@media (max-width: 1024px) {
|
|
87
87
|
header {
|
|
88
|
-
padding:
|
|
88
|
+
padding: calc(var(--vp-nav-height) + 2rem) 1.5rem 3rem !important;
|
|
89
89
|
}
|
|
90
90
|
header h1.large {
|
|
91
91
|
font-size: 3rem;
|
|
@@ -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,20 @@ 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
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
const normalizedImages = computed(() => {
|
|
94
101
|
if (!props.images) return [];
|
|
95
102
|
return props.images.map((img) => {
|
|
96
103
|
if (typeof img === "string") {
|
|
97
|
-
return { src: img, alt: "", caption: "" };
|
|
104
|
+
return { src: resolveUrl(img), alt: "", caption: "" };
|
|
98
105
|
}
|
|
99
106
|
return {
|
|
100
|
-
src: img.src
|
|
107
|
+
src: resolveUrl(img.src),
|
|
101
108
|
alt: img.alt || "",
|
|
102
109
|
caption: img.caption || "",
|
|
103
110
|
};
|
|
@@ -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,15 @@ 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
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const resolvedSrc = computed(() => resolveUrl(props.src));
|
|
114
|
+
const resolvedCompare = computed(() => resolveUrl(props.compare));
|
|
115
|
+
|
|
106
116
|
const sliderValue = ref(50);
|
|
107
117
|
</script>
|
|
108
118
|
|
|
@@ -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,12 @@ 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
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
200
|
+
}
|
|
201
|
+
|
|
195
202
|
const layoutMode = computed(() => {
|
|
196
203
|
// If explicitly 'grid', or if we detect link in image objects, default to grid
|
|
197
204
|
if (props.layout === "grid") return "grid";
|
|
@@ -211,13 +218,23 @@ const normalizedImages = computed(() => {
|
|
|
211
218
|
if (Array.isArray(props.images)) {
|
|
212
219
|
return props.images.map((img) => {
|
|
213
220
|
if (typeof img === "string") {
|
|
214
|
-
return {
|
|
221
|
+
return {
|
|
222
|
+
src: resolveUrl(img),
|
|
223
|
+
alt: "",
|
|
224
|
+
caption: "",
|
|
225
|
+
link: "",
|
|
226
|
+
width: "",
|
|
227
|
+
};
|
|
215
228
|
}
|
|
216
229
|
return {
|
|
217
|
-
src: img.src
|
|
230
|
+
src: resolveUrl(img.src),
|
|
218
231
|
alt: img.alt || "",
|
|
219
232
|
caption: img.caption || "",
|
|
220
|
-
link: img.link
|
|
233
|
+
link: img.link
|
|
234
|
+
? img.link.startsWith("/")
|
|
235
|
+
? withBase(img.link)
|
|
236
|
+
: img.link
|
|
237
|
+
: "",
|
|
221
238
|
width: img.width || "",
|
|
222
239
|
};
|
|
223
240
|
});
|
|
@@ -227,7 +244,13 @@ const normalizedImages = computed(() => {
|
|
|
227
244
|
.split(",")
|
|
228
245
|
.map((img) => img.trim())
|
|
229
246
|
.filter(Boolean)
|
|
230
|
-
.map((src) => ({
|
|
247
|
+
.map((src) => ({
|
|
248
|
+
src: resolveUrl(src),
|
|
249
|
+
alt: "",
|
|
250
|
+
caption: "",
|
|
251
|
+
link: "",
|
|
252
|
+
width: "",
|
|
253
|
+
}));
|
|
231
254
|
}
|
|
232
255
|
return [];
|
|
233
256
|
});
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
<section class="logo-section">
|
|
3
3
|
<div ref="logo-row" class="logo-row">
|
|
4
4
|
<component
|
|
5
|
-
v-for="logo in
|
|
5
|
+
v-for="logo in normalizedLogos"
|
|
6
|
+
:key="logo.alt"
|
|
6
7
|
:is="logo.link ? 'a' : 'div'"
|
|
7
8
|
:href="logo.link || undefined"
|
|
8
9
|
:target="logo.target || undefined"
|
|
@@ -15,11 +16,27 @@
|
|
|
15
16
|
</template>
|
|
16
17
|
|
|
17
18
|
<script setup>
|
|
18
|
-
import { useTemplateRef, onMounted } from "vue";
|
|
19
|
+
import { useTemplateRef, onMounted, computed } from "vue";
|
|
20
|
+
import { withBase } from "vitepress";
|
|
19
21
|
|
|
20
22
|
const props = defineProps(["baseHeight", "logos", "strength"]);
|
|
21
23
|
const logoRow = useTemplateRef("logo-row");
|
|
22
24
|
|
|
25
|
+
function resolveUrl(url) {
|
|
26
|
+
if (!url) return "";
|
|
27
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
28
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const normalizedLogos = computed(() => {
|
|
32
|
+
if (!props.logos) return [];
|
|
33
|
+
return props.logos.map((logo) => ({
|
|
34
|
+
...logo,
|
|
35
|
+
image: resolveUrl(logo.image),
|
|
36
|
+
link: logo.link ? resolveUrl(logo.link) : undefined,
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
|
|
23
40
|
onMounted(() => {
|
|
24
41
|
// Function to update CSS variables and display values
|
|
25
42
|
function updateVariables() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="top-nav at-top row center-align">
|
|
3
3
|
<nav class="large-padding no-margin center-align nav-mobile">
|
|
4
|
-
<a href="/">
|
|
4
|
+
<a :href="withBase('/')">
|
|
5
5
|
<img
|
|
6
6
|
:src="withBase(theme.logo.light)"
|
|
7
7
|
:alt="`${site.title} logo`"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
</button>
|
|
21
21
|
<dialog id="mobile-menu" class="max">
|
|
22
22
|
<nav>
|
|
23
|
-
<a data-ui="#mobile-menu" href="/">
|
|
23
|
+
<a data-ui="#mobile-menu" :href="withBase('/')">
|
|
24
24
|
<img
|
|
25
25
|
:src="withBase(theme.logo.light)"
|
|
26
26
|
:alt="`${site.title} logo`"
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
</dialog>
|
|
120
120
|
</nav>
|
|
121
121
|
<nav class="large-padding no-margin center-align row holder nav-desktop">
|
|
122
|
-
<a href="/">
|
|
122
|
+
<a :href="withBase('/')">
|
|
123
123
|
<img
|
|
124
124
|
:src="withBase(theme.logo.light)"
|
|
125
125
|
:alt="`${site.title} logo`"
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="tags-page">
|
|
3
3
|
<header class="tags-header">
|
|
4
|
-
<h1 class="tags-title">
|
|
5
|
-
Blog Tags
|
|
6
|
-
</h1>
|
|
4
|
+
<h1 class="tags-title">Blog Tags</h1>
|
|
7
5
|
<p class="tags-subtitle">
|
|
8
6
|
Explore our blog posts by topics and technology pillars.
|
|
9
7
|
</p>
|
|
@@ -15,7 +13,7 @@
|
|
|
15
13
|
<a
|
|
16
14
|
v-for="tag in allTagsList"
|
|
17
15
|
:key="tag.name"
|
|
18
|
-
:href="`/tag/${tag.name}/`"
|
|
16
|
+
:href="withBase(`/tag/${tag.name}/`)"
|
|
19
17
|
:style="getTagStyle(tag.name)"
|
|
20
18
|
class="tag-item"
|
|
21
19
|
>
|
|
@@ -60,7 +58,9 @@
|
|
|
60
58
|
</ul>
|
|
61
59
|
<p class="legend-note">
|
|
62
60
|
For more detailed information on our technology solutions, visit the
|
|
63
|
-
<a href="/
|
|
61
|
+
<a :href="withBase('/solutions')" class="legend-link"
|
|
62
|
+
>Solutions & Services</a
|
|
63
|
+
>
|
|
64
64
|
page.
|
|
65
65
|
</p>
|
|
66
66
|
</footer>
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
|
|
70
70
|
<script setup>
|
|
71
71
|
import { computed } from "vue";
|
|
72
|
+
import { withBase } from "vitepress";
|
|
72
73
|
import { data as posts } from "../posts.data.js";
|
|
73
74
|
import { getTagColor, getTagStyle } from "../tagUtils.js";
|
|
74
75
|
|
|
@@ -180,7 +181,9 @@ const allTagsList = computed(() => {
|
|
|
180
181
|
padding: 2.5rem 2rem;
|
|
181
182
|
border-radius: 1rem;
|
|
182
183
|
border: 1px solid rgba(226, 232, 240, 0.5); /* border-slate-100/50 */
|
|
183
|
-
box-shadow:
|
|
184
|
+
box-shadow:
|
|
185
|
+
0 1px 3px 0 rgba(0, 0, 0, 0.05),
|
|
186
|
+
0 1px 2px -1px rgba(0, 0, 0, 0.05); /* shadow-sm */
|
|
184
187
|
margin-bottom: 3rem;
|
|
185
188
|
}
|
|
186
189
|
|
|
@@ -202,14 +205,19 @@ const allTagsList = computed(() => {
|
|
|
202
205
|
border-radius: 4px; /* small rounding to distinguish from full-rounded buttons */
|
|
203
206
|
text-decoration: none !important;
|
|
204
207
|
line-height: 1.2;
|
|
205
|
-
transition:
|
|
208
|
+
transition:
|
|
209
|
+
opacity 0.2s ease,
|
|
210
|
+
transform 0.15s ease,
|
|
211
|
+
box-shadow 0.2s ease;
|
|
206
212
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
.tag-item:hover {
|
|
210
216
|
opacity: 0.85;
|
|
211
217
|
transform: translateY(-1px);
|
|
212
|
-
box-shadow:
|
|
218
|
+
box-shadow:
|
|
219
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.05),
|
|
220
|
+
0 2px 4px -2px rgba(0, 0, 0, 0.05);
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
.tag-count {
|
|
@@ -26,12 +26,20 @@
|
|
|
26
26
|
v-for="member in sortedTeamMembers"
|
|
27
27
|
:key="member.id"
|
|
28
28
|
:title="member.name"
|
|
29
|
-
:image="
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
:image="
|
|
30
|
+
member.image
|
|
31
|
+
? withBase(member.image)
|
|
32
|
+
: withBase('/images/team/new_member.jpg')
|
|
33
|
+
"
|
|
34
|
+
:link="
|
|
35
|
+
memberHasPosts(member.id)
|
|
36
|
+
? {
|
|
37
|
+
text: 'View blog posts',
|
|
38
|
+
href: `/author/${member.id}/`,
|
|
39
|
+
target: '_self',
|
|
40
|
+
}
|
|
41
|
+
: undefined
|
|
42
|
+
"
|
|
35
43
|
variant="cover"
|
|
36
44
|
class="bg-surface-container-low team-member-card"
|
|
37
45
|
>
|
|
@@ -43,11 +51,7 @@
|
|
|
43
51
|
<span class="member-divider"></span>
|
|
44
52
|
|
|
45
53
|
<!-- Description / Bio -->
|
|
46
|
-
<span
|
|
47
|
-
v-if="member.bio"
|
|
48
|
-
class="member-bio"
|
|
49
|
-
v-html="member.bio"
|
|
50
|
-
></span>
|
|
54
|
+
<span v-if="member.bio" class="member-bio" v-html="member.bio"></span>
|
|
51
55
|
|
|
52
56
|
<!-- Social Links row at the bottom of slot -->
|
|
53
57
|
<span class="member-footer">
|
package/src/style.css
CHANGED
|
@@ -374,7 +374,11 @@ eox-feedback-button {
|
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
/* Constrain standard reading elements to 1000px max-width, while letting full-width custom page components span 1200px */
|
|
377
|
-
.layout-page
|
|
377
|
+
.layout-page
|
|
378
|
+
.VPPage
|
|
379
|
+
> div
|
|
380
|
+
> div
|
|
381
|
+
> :is(p, ul, ol, blockquote, h1, h2, h3, h4, h5, h6, pre, table) {
|
|
378
382
|
max-width: 1000px;
|
|
379
383
|
margin-inline-start: 0;
|
|
380
384
|
margin-inline-end: auto;
|
|
@@ -473,7 +477,9 @@ eox-feedback-button {
|
|
|
473
477
|
text-decoration: underline;
|
|
474
478
|
text-underline-offset: 3px;
|
|
475
479
|
text-decoration-thickness: 1px;
|
|
476
|
-
transition:
|
|
480
|
+
transition:
|
|
481
|
+
color 0.2s ease,
|
|
482
|
+
text-decoration-color 0.2s ease;
|
|
477
483
|
}
|
|
478
484
|
|
|
479
485
|
.layout-page .VPPage a:not(.button):hover {
|
|
@@ -505,3 +511,57 @@ eox-feedback-button {
|
|
|
505
511
|
overflow-x: auto;
|
|
506
512
|
}
|
|
507
513
|
|
|
514
|
+
/* Local Search styles overrides */
|
|
515
|
+
.VPLocalSearchBox .search-actions button {
|
|
516
|
+
height: 36px !important;
|
|
517
|
+
width: 36px !important;
|
|
518
|
+
min-width: 0 !important;
|
|
519
|
+
max-width: none !important;
|
|
520
|
+
border-radius: 50% !important;
|
|
521
|
+
padding: 0 !important;
|
|
522
|
+
display: inline-flex !important;
|
|
523
|
+
align-items: center !important;
|
|
524
|
+
justify-content: center !important;
|
|
525
|
+
border: none !important;
|
|
526
|
+
background: transparent !important;
|
|
527
|
+
box-shadow: none !important;
|
|
528
|
+
color: var(--vp-c-text-2) !important;
|
|
529
|
+
transition:
|
|
530
|
+
background-color 0.15s,
|
|
531
|
+
color 0.15s !important;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.VPLocalSearchBox .search-actions button:not([disabled]):hover {
|
|
535
|
+
background-color: var(--vp-c-default-soft) !important;
|
|
536
|
+
color: var(--vp-c-brand-1) !important;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.VPLocalSearchBox .search-actions button:disabled {
|
|
540
|
+
opacity: 0.37 !important;
|
|
541
|
+
background: transparent !important;
|
|
542
|
+
color: var(--vp-c-text-3) !important;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.VPLocalSearchBox .results {
|
|
546
|
+
list-style: none !important;
|
|
547
|
+
padding-left: 0 !important;
|
|
548
|
+
margin: 0 !important;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.VPLocalSearchBox .results li {
|
|
552
|
+
list-style: none !important;
|
|
553
|
+
margin: 0 !important;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/* Screen Reader Only Utility */
|
|
557
|
+
.sr-only {
|
|
558
|
+
position: absolute !important;
|
|
559
|
+
width: 1px !important;
|
|
560
|
+
height: 1px !important;
|
|
561
|
+
padding: 0 !important;
|
|
562
|
+
margin: -1px !important;
|
|
563
|
+
overflow: hidden !important;
|
|
564
|
+
clip: rect(0, 0, 0, 0) !important;
|
|
565
|
+
white-space: nowrap !important;
|
|
566
|
+
border-width: 0 !important;
|
|
567
|
+
}
|
package/src/tagUtils.js
CHANGED
|
@@ -103,31 +103,31 @@ export function getTagStyle(tag) {
|
|
|
103
103
|
return {
|
|
104
104
|
backgroundColor: "rgba(255, 94, 94, 0.15)",
|
|
105
105
|
border: "1px solid rgb(255, 94, 94)",
|
|
106
|
-
color: "#d32f2f"
|
|
106
|
+
color: "#d32f2f",
|
|
107
107
|
};
|
|
108
108
|
} else if (color.startsWith("rgb(0, 177, 176)")) {
|
|
109
109
|
return {
|
|
110
110
|
backgroundColor: "rgba(0, 177, 176, 0.15)",
|
|
111
111
|
border: "1px solid rgb(0, 177, 176)",
|
|
112
|
-
color: "#00796b"
|
|
112
|
+
color: "#00796b",
|
|
113
113
|
};
|
|
114
114
|
} else if (color.startsWith("rgb(219, 173, 66)")) {
|
|
115
115
|
return {
|
|
116
116
|
backgroundColor: "rgba(219, 173, 66, 0.15)",
|
|
117
117
|
border: "1px solid rgb(219, 173, 66)",
|
|
118
|
-
color: "#f57c00"
|
|
118
|
+
color: "#f57c00",
|
|
119
119
|
};
|
|
120
120
|
} else if (color.startsWith("rgb(218, 71, 228)")) {
|
|
121
121
|
return {
|
|
122
122
|
backgroundColor: "rgba(218, 71, 228, 0.15)",
|
|
123
123
|
border: "1px solid rgb(218, 71, 228)",
|
|
124
|
-
color: "#c2185b"
|
|
124
|
+
color: "#c2185b",
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
return {
|
|
128
128
|
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
129
129
|
border: "1px solid rgba(0, 0, 0, 0.1)",
|
|
130
|
-
color: "#475569"
|
|
130
|
+
color: "#475569",
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|