@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
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { withBase } from "vitepress";
|
|
4
|
+
import { data as authorsList } from "../../authors.data.js";
|
|
5
|
+
import { getTagColor } from "../../tagUtils.js";
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
3
8
|
title: String,
|
|
4
9
|
content: String,
|
|
5
10
|
image: {
|
|
@@ -7,7 +12,7 @@ const { title, content, link, secondaryLink, icon } = defineProps({
|
|
|
7
12
|
required: false,
|
|
8
13
|
},
|
|
9
14
|
chips: {
|
|
10
|
-
/** @type {import('vue').PropType<{ text: string; class?: string }[]>} */
|
|
15
|
+
/** @type {import('vue').PropType<{ text: string; link?: string; href?: string; class?: string }[]>} */
|
|
11
16
|
type: Array,
|
|
12
17
|
required: false,
|
|
13
18
|
default: () => [],
|
|
@@ -29,49 +34,265 @@ const { title, content, link, secondaryLink, icon } = defineProps({
|
|
|
29
34
|
required: false,
|
|
30
35
|
},
|
|
31
36
|
icon: {
|
|
32
|
-
/** @type {import('vue').PropType<{ html: string, width?: number, height?: number }>} */
|
|
33
|
-
type: Object,
|
|
37
|
+
/** @type {import('vue').PropType<string | { html: string, width?: number, height?: number }>} */
|
|
38
|
+
type: [Object, String],
|
|
39
|
+
required: false,
|
|
40
|
+
},
|
|
41
|
+
date: {
|
|
42
|
+
type: String,
|
|
43
|
+
required: false,
|
|
44
|
+
},
|
|
45
|
+
authors: {
|
|
46
|
+
type: Array,
|
|
34
47
|
required: false,
|
|
48
|
+
default: () => [],
|
|
49
|
+
},
|
|
50
|
+
variant: {
|
|
51
|
+
type: String,
|
|
52
|
+
required: false,
|
|
53
|
+
default: "default",
|
|
35
54
|
},
|
|
36
55
|
});
|
|
56
|
+
|
|
57
|
+
function resolveUrl(url) {
|
|
58
|
+
if (!url) return "";
|
|
59
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
60
|
+
const base = withBase("/");
|
|
61
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
62
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolvedImage = computed(() => resolveUrl(props.image));
|
|
66
|
+
const resolvedLinkHref = computed(() =>
|
|
67
|
+
props.link?.href ? resolveUrl(props.link.href) : undefined,
|
|
68
|
+
);
|
|
69
|
+
const resolvedSecondaryLinkHref = computed(() =>
|
|
70
|
+
props.secondaryLink?.href ? resolveUrl(props.secondaryLink.href) : undefined,
|
|
71
|
+
);
|
|
72
|
+
|
|
37
73
|
const iconStyle = {
|
|
38
|
-
width: (icon?.width ?? 40) + "px",
|
|
39
|
-
height: (icon?.height ?? 40) + "px",
|
|
74
|
+
width: (props.icon?.width ?? 40) + "px",
|
|
75
|
+
height: (props.icon?.height ?? 40) + "px",
|
|
40
76
|
};
|
|
77
|
+
|
|
78
|
+
// Check if we should render a full-width cover image
|
|
79
|
+
const isCoverLayout = computed(() => {
|
|
80
|
+
return (
|
|
81
|
+
props.image &&
|
|
82
|
+
(props.variant === "cover" || props.date || props.authors.length)
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Check if the card itself is fully clickable (has a link but NO link text)
|
|
87
|
+
const isClickableCard = computed(() => {
|
|
88
|
+
return props.link && !props.link.text;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
function getAuthorName(authorId) {
|
|
92
|
+
if (!authorId) return "EOX Experts";
|
|
93
|
+
const author = authorsList.find(
|
|
94
|
+
(a) => a.id.toLowerCase().trim() === authorId.toLowerCase().trim(),
|
|
95
|
+
);
|
|
96
|
+
return author ? author.name : authorId;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getTagStyle(tag) {
|
|
100
|
+
const color = getTagColor(tag);
|
|
101
|
+
if (color.startsWith("rgb(255, 94, 94)")) {
|
|
102
|
+
return {
|
|
103
|
+
backgroundColor: "rgba(255, 94, 94, 0.15)",
|
|
104
|
+
border: "1px solid rgb(255, 94, 94)",
|
|
105
|
+
color: "#d32f2f",
|
|
106
|
+
};
|
|
107
|
+
} else if (color.startsWith("rgb(0, 177, 176)")) {
|
|
108
|
+
return {
|
|
109
|
+
backgroundColor: "rgba(0, 177, 176, 0.15)",
|
|
110
|
+
border: "1px solid rgb(0, 177, 176)",
|
|
111
|
+
color: "#00796b",
|
|
112
|
+
};
|
|
113
|
+
} else if (color.startsWith("rgb(219, 173, 66)")) {
|
|
114
|
+
return {
|
|
115
|
+
backgroundColor: "rgba(219, 173, 66, 0.15)",
|
|
116
|
+
border: "1px solid rgb(219, 173, 66)",
|
|
117
|
+
color: "#f57c00",
|
|
118
|
+
};
|
|
119
|
+
} else if (color.startsWith("rgb(218, 71, 228)")) {
|
|
120
|
+
return {
|
|
121
|
+
backgroundColor: "rgba(218, 71, 228, 0.15)",
|
|
122
|
+
border: "1px solid rgb(218, 71, 228)",
|
|
123
|
+
color: "#c2185b",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
128
|
+
border: "1px solid rgba(0, 0, 0, 0.1)",
|
|
129
|
+
color: "#475569",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatDate(dateString) {
|
|
134
|
+
if (!dateString) return "";
|
|
135
|
+
const date = new Date(dateString);
|
|
136
|
+
return date.toLocaleDateString("en-US", {
|
|
137
|
+
year: "numeric",
|
|
138
|
+
month: "short",
|
|
139
|
+
day: "numeric",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
41
142
|
</script>
|
|
143
|
+
|
|
42
144
|
<template>
|
|
43
145
|
<article
|
|
44
|
-
class="vertical large-padding no-margin"
|
|
45
|
-
:class="
|
|
146
|
+
class="vertical large-padding no-margin theme-feature-card"
|
|
147
|
+
:class="[
|
|
148
|
+
isClickableCard ? 'small-elevate clickable-card' : 'no-elevate',
|
|
149
|
+
isCoverLayout ? 'has-cover-image' : '',
|
|
150
|
+
]"
|
|
46
151
|
>
|
|
47
|
-
|
|
152
|
+
<!-- Cover Image for cover/blog/rich cards -->
|
|
153
|
+
<a
|
|
154
|
+
v-if="isCoverLayout && resolvedLinkHref"
|
|
155
|
+
:href="resolvedLinkHref"
|
|
156
|
+
:target="link?.target ?? '_blank'"
|
|
157
|
+
class="card-cover-link"
|
|
158
|
+
>
|
|
159
|
+
<img
|
|
160
|
+
:src="resolvedImage"
|
|
161
|
+
alt=""
|
|
162
|
+
class="card-cover-image"
|
|
163
|
+
loading="lazy"
|
|
164
|
+
/>
|
|
165
|
+
</a>
|
|
166
|
+
<div v-else-if="isCoverLayout" class="card-cover-link">
|
|
167
|
+
<img
|
|
168
|
+
:src="resolvedImage"
|
|
169
|
+
alt=""
|
|
170
|
+
class="card-cover-image"
|
|
171
|
+
loading="lazy"
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="card-content-wrapper">
|
|
48
176
|
<i
|
|
49
177
|
v-if="typeof icon === 'string' && icon.startsWith('mdi-')"
|
|
50
178
|
:class="`mdi ${icon}`"
|
|
51
179
|
></i>
|
|
180
|
+
<img
|
|
181
|
+
v-else-if="typeof icon === 'string'"
|
|
182
|
+
:src="resolveUrl(icon)"
|
|
183
|
+
alt=""
|
|
184
|
+
class="icon"
|
|
185
|
+
style="
|
|
186
|
+
height: 36px;
|
|
187
|
+
width: auto;
|
|
188
|
+
max-width: 120px;
|
|
189
|
+
object-fit: contain;
|
|
190
|
+
margin-bottom: 0.75rem;
|
|
191
|
+
border-radius: 0 !important;
|
|
192
|
+
"
|
|
193
|
+
/>
|
|
52
194
|
<div
|
|
53
|
-
v-else-if="icon"
|
|
195
|
+
v-else-if="icon && icon.html"
|
|
54
196
|
:style="iconStyle"
|
|
55
197
|
v-html="icon.html"
|
|
56
198
|
class="icon"
|
|
57
199
|
></div>
|
|
58
|
-
|
|
59
|
-
<
|
|
200
|
+
<!-- Render small icon image only if NOT rendering a full cover image -->
|
|
201
|
+
<img
|
|
202
|
+
v-else-if="image && !isCoverLayout"
|
|
203
|
+
:src="resolvedImage"
|
|
204
|
+
alt=""
|
|
205
|
+
class="icon"
|
|
206
|
+
/>
|
|
207
|
+
|
|
208
|
+
<!-- Metadata & Tags directly under place/author -->
|
|
209
|
+
<div v-if="date || (authors && authors.length)" class="metadata-wrapper">
|
|
210
|
+
<div class="blog-metadata">
|
|
211
|
+
<span v-if="date" class="date-span">
|
|
212
|
+
<i class="mdi mdi-clock-outline"></i>
|
|
213
|
+
<time :datetime="date">{{ formatDate(date) }}</time>
|
|
214
|
+
</span>
|
|
215
|
+
<span v-if="authors && authors.length" class="author-span">
|
|
216
|
+
<span class="author-icon">
|
|
217
|
+
<i class="mdi mdi-account-outline"></i>
|
|
218
|
+
</span>
|
|
219
|
+
<span
|
|
220
|
+
v-for="(authorId, idx) in authors"
|
|
221
|
+
:key="authorId"
|
|
222
|
+
class="author-item"
|
|
223
|
+
>
|
|
224
|
+
<a :href="withBase(`/author/${authorId}/`)" class="author-link">{{
|
|
225
|
+
getAuthorName(authorId)
|
|
226
|
+
}}</a
|
|
227
|
+
><span v-if="idx < authors.length - 1" class="author-separator"
|
|
228
|
+
>,</span
|
|
229
|
+
>
|
|
230
|
+
</span>
|
|
231
|
+
</span>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<!-- Tags directly under metadata if chips are provided and this is a rich card -->
|
|
235
|
+
<div v-if="chips && chips.length" class="blog-tags-container">
|
|
236
|
+
<a
|
|
237
|
+
v-for="chip in chips"
|
|
238
|
+
:key="chip.text"
|
|
239
|
+
:href="
|
|
240
|
+
chip.link
|
|
241
|
+
? resolveUrl(chip.link)
|
|
242
|
+
: chip.href
|
|
243
|
+
? resolveUrl(chip.href)
|
|
244
|
+
: withBase(`/tag/${chip.text}/`)
|
|
245
|
+
"
|
|
246
|
+
:style="getTagStyle(chip.text)"
|
|
247
|
+
class="tag-link"
|
|
248
|
+
>
|
|
249
|
+
{{ chip.text }}
|
|
250
|
+
</a>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<h5 class="small card-title-text">
|
|
255
|
+
<a v-if="resolvedLinkHref" :href="resolvedLinkHref">{{ title }}</a>
|
|
256
|
+
<span v-else>{{ title }}</span>
|
|
257
|
+
</h5>
|
|
258
|
+
|
|
259
|
+
<!-- Standard chips (rendered only for normal cards) -->
|
|
60
260
|
<div
|
|
61
|
-
v-if="chips && chips.length"
|
|
261
|
+
v-if="chips && chips.length && !date && !authors.length"
|
|
62
262
|
class="row wrap"
|
|
63
263
|
style="gap: 0.5rem; margin-bottom: 0.5rem"
|
|
64
264
|
>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
265
|
+
<template v-for="chip in chips" :key="chip.text">
|
|
266
|
+
<a
|
|
267
|
+
v-if="chip.link || chip.href"
|
|
268
|
+
:href="
|
|
269
|
+
chip.link
|
|
270
|
+
? resolveUrl(chip.link)
|
|
271
|
+
: chip.href
|
|
272
|
+
? resolveUrl(chip.href)
|
|
273
|
+
: undefined
|
|
274
|
+
"
|
|
275
|
+
:style="getTagStyle(chip.text)"
|
|
276
|
+
class="chip small-chip border clickable-chip"
|
|
277
|
+
:class="chip.class"
|
|
278
|
+
>
|
|
279
|
+
{{ chip.text }}
|
|
280
|
+
</a>
|
|
281
|
+
<span
|
|
282
|
+
v-else
|
|
283
|
+
:style="getTagStyle(chip.text)"
|
|
284
|
+
class="chip small-chip border clickable-chip"
|
|
285
|
+
:class="chip.class"
|
|
286
|
+
>
|
|
287
|
+
{{ chip.text }}
|
|
288
|
+
</span>
|
|
289
|
+
</template>
|
|
73
290
|
</div>
|
|
74
|
-
|
|
291
|
+
|
|
292
|
+
<p
|
|
293
|
+
v-if="metadata && metadata.length && !date && !authors.length"
|
|
294
|
+
class="small-text"
|
|
295
|
+
>
|
|
75
296
|
<span
|
|
76
297
|
v-for="(item, index) in metadata"
|
|
77
298
|
:key="index"
|
|
@@ -87,19 +308,20 @@ const iconStyle = {
|
|
|
87
308
|
</span>
|
|
88
309
|
</p>
|
|
89
310
|
|
|
90
|
-
<p v-if="$slots.default">
|
|
311
|
+
<p v-if="$slots.default" class="card-body-text">
|
|
91
312
|
<slot>{{ content }}</slot>
|
|
92
313
|
</p>
|
|
93
|
-
<div v-else v-html="content"></div>
|
|
314
|
+
<div v-else-if="content" v-html="content" class="card-body-text"></div>
|
|
94
315
|
</div>
|
|
316
|
+
|
|
95
317
|
<nav
|
|
96
318
|
v-if="link || secondaryLink"
|
|
97
|
-
class="row wrap align-center"
|
|
319
|
+
class="row wrap align-center card-navigation"
|
|
98
320
|
style="gap: 1.5rem; margin-right: auto; position: relative; z-index: 11"
|
|
99
321
|
>
|
|
100
322
|
<a
|
|
101
323
|
v-if="link && !!link.text"
|
|
102
|
-
:href="
|
|
324
|
+
:href="resolvedLinkHref"
|
|
103
325
|
:target="link.target ?? '_blank'"
|
|
104
326
|
class="button transparent bold primary-text no-padding"
|
|
105
327
|
>
|
|
@@ -110,7 +332,7 @@ const iconStyle = {
|
|
|
110
332
|
</a>
|
|
111
333
|
<a
|
|
112
334
|
v-if="secondaryLink"
|
|
113
|
-
:href="
|
|
335
|
+
:href="resolvedSecondaryLinkHref"
|
|
114
336
|
:target="secondaryLink.target ?? '_blank'"
|
|
115
337
|
class="button transparent primary-text no-padding secondary-link"
|
|
116
338
|
>
|
|
@@ -121,6 +343,7 @@ const iconStyle = {
|
|
|
121
343
|
</nav>
|
|
122
344
|
</article>
|
|
123
345
|
</template>
|
|
346
|
+
|
|
124
347
|
<style scoped>
|
|
125
348
|
/**
|
|
126
349
|
* Undo Vitepress missing with @eox/ui styles
|
|
@@ -142,17 +365,37 @@ const iconStyle = {
|
|
|
142
365
|
/**
|
|
143
366
|
* Custom styles
|
|
144
367
|
*/
|
|
145
|
-
|
|
368
|
+
.theme-feature-card {
|
|
146
369
|
justify-content: space-between;
|
|
147
|
-
transition: all 0.3s ease-in-out;
|
|
148
370
|
width: 100%;
|
|
371
|
+
border-radius: 16px !important;
|
|
372
|
+
overflow: hidden !important;
|
|
373
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
|
|
149
374
|
}
|
|
375
|
+
|
|
376
|
+
/* Clickable Cards Hover System */
|
|
377
|
+
.theme-feature-card.clickable-card {
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
transition:
|
|
380
|
+
transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
|
|
381
|
+
box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
|
|
382
|
+
will-change: transform, box-shadow;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.theme-feature-card.clickable-card:hover {
|
|
386
|
+
transform: translateY(-4px) !important;
|
|
387
|
+
box-shadow:
|
|
388
|
+
0 12px 28px rgba(0, 0, 0, 0.12),
|
|
389
|
+
0 4px 8px rgba(0, 0, 0, 0.04) !important;
|
|
390
|
+
}
|
|
391
|
+
|
|
150
392
|
.small-chip {
|
|
151
393
|
font-size: 0.75rem;
|
|
152
|
-
padding:
|
|
394
|
+
padding: 4px 10px !important;
|
|
153
395
|
height: auto;
|
|
154
|
-
line-height:
|
|
396
|
+
line-height: 1.2 !important;
|
|
155
397
|
margin: 0;
|
|
398
|
+
border-radius: 6px !important;
|
|
156
399
|
}
|
|
157
400
|
.arrow {
|
|
158
401
|
transition: transform 0.2s;
|
|
@@ -163,4 +406,192 @@ article {
|
|
|
163
406
|
.button.transparent:hover:after {
|
|
164
407
|
background: none;
|
|
165
408
|
}
|
|
409
|
+
|
|
410
|
+
/* Card with Cover Image Layout */
|
|
411
|
+
.theme-feature-card.has-cover-image {
|
|
412
|
+
padding: 0 !important;
|
|
413
|
+
display: flex;
|
|
414
|
+
flex-direction: column;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.theme-feature-card.has-cover-image .card-cover-link {
|
|
418
|
+
display: block;
|
|
419
|
+
width: 100%;
|
|
420
|
+
aspect-ratio: 16 / 9;
|
|
421
|
+
overflow: hidden;
|
|
422
|
+
position: relative;
|
|
423
|
+
flex-shrink: 0;
|
|
424
|
+
border-top-left-radius: 16px !important;
|
|
425
|
+
border-top-right-radius: 16px !important;
|
|
426
|
+
border-bottom-left-radius: 0 !important;
|
|
427
|
+
border-bottom-right-radius: 0 !important;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.theme-feature-card.has-cover-image .card-cover-image {
|
|
431
|
+
width: 100%;
|
|
432
|
+
height: 100%;
|
|
433
|
+
object-fit: cover;
|
|
434
|
+
transition: transform 0.3s ease;
|
|
435
|
+
border-top-left-radius: 16px !important;
|
|
436
|
+
border-top-right-radius: 16px !important;
|
|
437
|
+
border-bottom-left-radius: 0 !important;
|
|
438
|
+
border-bottom-right-radius: 0 !important;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Zoom Image on Hover ONLY if the card itself is clickable */
|
|
442
|
+
.theme-feature-card.has-cover-image.clickable-card .card-cover-image {
|
|
443
|
+
transition: transform 0.5s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.theme-feature-card.has-cover-image.clickable-card:hover .card-cover-image {
|
|
447
|
+
transform: scale(1.04) !important;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.theme-feature-card.has-cover-image .card-content-wrapper {
|
|
451
|
+
padding: 1.5rem;
|
|
452
|
+
display: flex;
|
|
453
|
+
flex-direction: column;
|
|
454
|
+
flex-grow: 1;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.theme-feature-card.has-cover-image .card-navigation {
|
|
458
|
+
padding: 0 1.5rem 1.5rem 1.5rem;
|
|
459
|
+
margin-top: auto;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/* Metadata Styling */
|
|
463
|
+
.metadata-wrapper {
|
|
464
|
+
margin-bottom: 16px;
|
|
465
|
+
text-align: left;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.blog-metadata {
|
|
469
|
+
display: block !important;
|
|
470
|
+
font-size: 12px !important;
|
|
471
|
+
color: #64748b !important;
|
|
472
|
+
line-height: 1.6 !important;
|
|
473
|
+
margin-bottom: 8px;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.date-span {
|
|
477
|
+
display: inline-flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
gap: 4px;
|
|
480
|
+
white-space: nowrap !important;
|
|
481
|
+
margin-right: 12px;
|
|
482
|
+
vertical-align: middle;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.date-span i {
|
|
486
|
+
font-size: 13px !important;
|
|
487
|
+
color: #64748b !important;
|
|
488
|
+
vertical-align: middle;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.author-span {
|
|
492
|
+
display: inline;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.author-icon {
|
|
496
|
+
display: inline-flex;
|
|
497
|
+
align-items: center;
|
|
498
|
+
margin-right: 4px;
|
|
499
|
+
vertical-align: middle;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.author-icon i {
|
|
503
|
+
font-size: 13px !important;
|
|
504
|
+
color: #64748b !important;
|
|
505
|
+
vertical-align: middle;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.author-item {
|
|
509
|
+
display: inline;
|
|
510
|
+
white-space: nowrap !important;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.author-link {
|
|
514
|
+
color: #64748b !important;
|
|
515
|
+
text-decoration: none !important;
|
|
516
|
+
font-size: 12px !important;
|
|
517
|
+
font-weight: 600;
|
|
518
|
+
transition: color 0.15s ease;
|
|
519
|
+
vertical-align: middle;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.author-link:hover {
|
|
523
|
+
color: var(--vp-c-brand) !important;
|
|
524
|
+
text-decoration: underline !important;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.author-separator {
|
|
528
|
+
color: #64748b !important;
|
|
529
|
+
font-size: 12px !important;
|
|
530
|
+
vertical-align: middle;
|
|
531
|
+
margin-right: 4px;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.date-span time {
|
|
535
|
+
font-size: 12px !important;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/* Tags Styling */
|
|
539
|
+
.blog-tags-container {
|
|
540
|
+
display: flex;
|
|
541
|
+
flex-wrap: wrap;
|
|
542
|
+
gap: 6px;
|
|
543
|
+
margin-top: 8px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.tag-link {
|
|
547
|
+
display: inline-block !important;
|
|
548
|
+
font-size: 12px !important;
|
|
549
|
+
font-weight: 700 !important;
|
|
550
|
+
padding: 4px 10px !important;
|
|
551
|
+
border-radius: 6px !important;
|
|
552
|
+
text-decoration: none !important;
|
|
553
|
+
line-height: 1.2 !important;
|
|
554
|
+
transition: opacity 0.2s ease;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.tag-link:hover {
|
|
558
|
+
opacity: 0.8 !important;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/* card title overrides to make sure it respects the 20px request */
|
|
562
|
+
h5.small.card-title-text,
|
|
563
|
+
.card-title-text,
|
|
564
|
+
.VPHome .vp-doc h5.small.card-title-text {
|
|
565
|
+
font-size: 20px !important;
|
|
566
|
+
line-height: 1.4 !important;
|
|
567
|
+
font-weight: 700 !important;
|
|
568
|
+
margin-bottom: 0.75rem !important;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.card-title-text a {
|
|
572
|
+
color: #0f172a !important;
|
|
573
|
+
text-decoration: none !important;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.card-title-text a:hover {
|
|
577
|
+
color: var(--vp-c-brand) !important;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.card-body-text {
|
|
581
|
+
font-size: 0.875rem !important;
|
|
582
|
+
line-height: 1.6 !important;
|
|
583
|
+
color: #475569 !important;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* Clickable standard chips styling */
|
|
587
|
+
.clickable-chip {
|
|
588
|
+
text-decoration: none !important;
|
|
589
|
+
color: var(--vp-c-text-2) !important;
|
|
590
|
+
transition: all 0.2s ease;
|
|
591
|
+
border-radius: 6px !important;
|
|
592
|
+
border: 1px solid transparent !important;
|
|
593
|
+
}
|
|
594
|
+
.clickable-chip:hover {
|
|
595
|
+
opacity: 0.8 !important;
|
|
596
|
+
}
|
|
166
597
|
</style>
|
|
@@ -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,22 @@ 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
|
+
const base = withBase("/");
|
|
49
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
50
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const resolvedImage = computed(() => resolveUrl(props.image));
|
|
54
|
+
const resolvedLinkHref = computed(() =>
|
|
55
|
+
props.link?.href ? resolveUrl(props.link.href) : undefined,
|
|
56
|
+
);
|
|
57
|
+
const resolvedSecondaryLinkHref = computed(() =>
|
|
58
|
+
props.secondaryLink?.href ? resolveUrl(props.secondaryLink.href) : undefined,
|
|
59
|
+
);
|
|
60
|
+
|
|
44
61
|
const cardStyle = computed(() => ({
|
|
45
62
|
minHeight: props.minHeight,
|
|
46
63
|
}));
|
|
@@ -52,7 +69,12 @@ const cardStyle = computed(() => ({
|
|
|
52
69
|
:class="link && !link.text ? 'small-elevate' : 'no-elevate'"
|
|
53
70
|
:style="cardStyle"
|
|
54
71
|
>
|
|
55
|
-
<img
|
|
72
|
+
<img
|
|
73
|
+
v-if="image"
|
|
74
|
+
:src="resolvedImage"
|
|
75
|
+
alt=""
|
|
76
|
+
class="image-overlay-card-image"
|
|
77
|
+
/>
|
|
56
78
|
<div class="image-overlay-card-overlay"></div>
|
|
57
79
|
<div class="image-overlay-card-content padding">
|
|
58
80
|
<div style="padding-top: 3rem">
|
|
@@ -103,7 +125,7 @@ const cardStyle = computed(() => ({
|
|
|
103
125
|
<a
|
|
104
126
|
v-if="link && link.text"
|
|
105
127
|
class="button small white black-text"
|
|
106
|
-
:href="
|
|
128
|
+
:href="resolvedLinkHref"
|
|
107
129
|
:target="link.target ?? '_blank'"
|
|
108
130
|
>
|
|
109
131
|
{{ link.text }}
|
|
@@ -111,7 +133,7 @@ const cardStyle = computed(() => ({
|
|
|
111
133
|
<a
|
|
112
134
|
v-if="secondaryLink"
|
|
113
135
|
class="button small border white-text secondary-link"
|
|
114
|
-
:href="
|
|
136
|
+
:href="resolvedSecondaryLinkHref"
|
|
115
137
|
:target="secondaryLink.target ?? '_blank'"
|
|
116
138
|
>
|
|
117
139
|
{{ secondaryLink.text }}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { h } from "vue";
|
|
1
2
|
import FeatureSection from "./FeatureSection.vue";
|
|
2
3
|
|
|
3
4
|
describe("<FeatureSection />", () => {
|
|
@@ -60,4 +61,19 @@ describe("<FeatureSection />", () => {
|
|
|
60
61
|
cy.get(".feature-section").should("have.class", "dark");
|
|
61
62
|
cy.get(".feature-section").should("have.class", "reverse");
|
|
62
63
|
});
|
|
64
|
+
|
|
65
|
+
it("has padding-block-start 0 when following a title", () => {
|
|
66
|
+
const TestWrapper = {
|
|
67
|
+
render() {
|
|
68
|
+
return h("div", [
|
|
69
|
+
h("h2", "Products & Services"),
|
|
70
|
+
h("p", "Subtitle paragraph"),
|
|
71
|
+
h(FeatureSection, defaultProps),
|
|
72
|
+
]);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
cy.mount(TestWrapper);
|
|
77
|
+
cy.get(".feature-section").should("have.css", "padding-top", "0px");
|
|
78
|
+
});
|
|
63
79
|
});
|
|
@@ -120,16 +120,11 @@ const props = defineProps([
|
|
|
120
120
|
}
|
|
121
121
|
.feature-section.landing .holder {
|
|
122
122
|
padding: 32px 0;
|
|
123
|
-
background: var(--surface-container-
|
|
123
|
+
background: var(--surface-container-lowest, #ffffff);
|
|
124
124
|
}
|
|
125
125
|
.feature-section.dark .holder {
|
|
126
|
-
background:
|
|
127
|
-
background:
|
|
128
|
-
74.48% 130.95% at 50% -12.27%,
|
|
129
|
-
hsl(from var(--primary) h s calc(l + 7)) 0%,
|
|
130
|
-
var(--primary) 45%,
|
|
131
|
-
hsl(from var(--primary) h s calc(l - 20)) 100%
|
|
132
|
-
);
|
|
126
|
+
background: #000c14;
|
|
127
|
+
background: linear-gradient(180deg, var(--primary, #004170) 0%, #000c14 100%);
|
|
133
128
|
color: #fff;
|
|
134
129
|
}
|
|
135
130
|
.feature-section nav {
|