@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.
Files changed (53) hide show
  1. package/.gitlab-ci.yml +4 -1
  2. package/README.md +88 -11
  3. package/cypress/support/mocks/authors.data.js +14 -0
  4. package/cypress/support/mocks/helpers.js +65 -4
  5. package/cypress/support/mocks/vitepress.js +8 -1
  6. package/cypress.config.js +14 -0
  7. package/package.json +3 -1
  8. package/src/Layout.vue +69 -1
  9. package/src/authors.data.js +14 -0
  10. package/src/components/BlogList.vue +655 -0
  11. package/src/components/BlogPost.vue +472 -0
  12. package/src/components/CTASection.vue +18 -4
  13. package/src/components/FeatureCard/Index.vue +462 -31
  14. package/src/components/FeatureCard/Poster.vue +25 -3
  15. package/src/components/FeatureSection.cy.js +16 -0
  16. package/src/components/FeatureSection.vue +3 -8
  17. package/src/components/FeaturesGallery.vue +34 -2
  18. package/src/components/Footer.cy.js +52 -2
  19. package/src/components/Footer.vue +17 -5
  20. package/src/components/HeroSection.cy.js +20 -0
  21. package/src/components/HeroSection.vue +140 -25
  22. package/src/components/ImageCarousel.vue +11 -2
  23. package/src/components/ImageElement.vue +16 -4
  24. package/src/components/ImageGallery.vue +29 -4
  25. package/src/components/LogoSection.vue +26 -3
  26. package/src/components/MobileNavDropdown.vue +13 -3
  27. package/src/components/NavBar.cy.js +15 -0
  28. package/src/components/NavBar.vue +149 -21
  29. package/src/components/NavDropdown.vue +12 -2
  30. package/src/components/OpenPositions.vue +292 -0
  31. package/src/components/SectionHeader.cy.js +54 -0
  32. package/src/components/SectionHeader.vue +115 -0
  33. package/src/components/TagList.vue +311 -0
  34. package/src/components/TeamPage.vue +347 -0
  35. package/src/components/TestimonialsSection.cy.js +76 -0
  36. package/src/components/TestimonialsSection.vue +368 -0
  37. package/src/components/TextSection.cy.js +115 -0
  38. package/src/components/TextSection.vue +396 -0
  39. package/src/helpers.js +182 -37
  40. package/src/helpers.test.js +181 -0
  41. package/src/index.js +16 -0
  42. package/src/pages/author/[author].md +5 -0
  43. package/src/pages/author/[author].paths.js +60 -0
  44. package/src/pages/blog.md +5 -0
  45. package/src/pages/tag/[tag].md +5 -0
  46. package/src/pages/tag/[tag].paths.js +49 -0
  47. package/src/pages/tag.md +5 -0
  48. package/src/pages/team.md +5 -0
  49. package/src/posts.data.js +134 -0
  50. package/src/style.css +389 -8
  51. package/src/tagUtils.js +148 -0
  52. package/src/team.data.js +1 -1
  53. package/src/vitepressConfig.mjs +101 -1
@@ -1,5 +1,10 @@
1
1
  <script setup>
2
- const { title, content, link, secondaryLink, icon } = defineProps({
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="link && !link.text ? 'small-elevate' : 'no-elevate'"
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
- <div>
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
- <img v-else-if="image" :src="image" alt="" class="icon" />
59
- <h5 class="small">{{ title }}</h5>
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
- <span
66
- v-for="chip in chips"
67
- :key="chip.text"
68
- class="chip small-chip border"
69
- :class="chip.class"
70
- >
71
- {{ chip.text }}
72
- </span>
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
- <p v-if="metadata && metadata.length" class="small-text">
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="link.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="secondaryLink.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
- article {
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: 0.125rem 0.375rem;
394
+ padding: 4px 10px !important;
153
395
  height: auto;
154
- line-height: normal;
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 v-if="image" :src="image" alt="" class="image-overlay-card-image" />
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="link.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="secondaryLink.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-low);
123
+ background: var(--surface-container-lowest, #ffffff);
124
124
  }
125
125
  .feature-section.dark .holder {
126
- background: var(--primary);
127
- background: radial-gradient(
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 {