@eox/pages-theme-eox 2.0.0-alpha.7 → 2.0.0-alpha.9
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/README.md +21 -46
- package/cypress/support/mocks/helpers.js +25 -3
- package/package.json +3 -4
- package/src/Layout.vue +6 -3
- package/src/components/BlogList.vue +38 -25
- package/src/components/CTASection.vue +1 -1
- package/src/components/FeatureCard/Index.vue +16 -1
- package/src/components/FeatureSection.vue +3 -8
- package/src/components/FeaturesGallery.vue +5 -0
- package/src/components/Footer.cy.js +40 -2
- package/src/components/Footer.vue +5 -3
- package/src/components/HeroSection.vue +103 -15
- package/src/components/LogoSection.vue +1 -0
- package/src/components/MobileNavDropdown.vue +6 -3
- package/src/components/NavBar.vue +108 -15
- package/src/components/NavDropdown.vue +5 -2
- package/src/components/TagList.vue +16 -3
- package/src/components/TeamPage.vue +21 -5
- package/src/components/TestimonialsSection.vue +119 -16
- package/src/components/TextSection.cy.js +61 -0
- package/src/components/TextSection.vue +384 -0
- package/src/components/Tutorial.vue +3 -2
- package/src/helpers.js +13 -5
- package/src/helpers.test.js +28 -0
- package/src/index.js +2 -0
- package/src/pages/author/[author].md +1 -0
- package/src/pages/blog.md +1 -0
- package/src/pages/tag/[tag].md +1 -0
- package/src/pages/tag.md +1 -0
- package/src/pages/team.md +1 -0
- package/src/style.css +19 -8
- package/src/vitepressConfig.mjs +0 -6
- package/src/llms/index.js +0 -64
- package/src/llms/llms.test.js +0 -980
- package/src/llms/storiesPlugin.js +0 -771
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-for="item in items">
|
|
3
|
-
<li
|
|
3
|
+
<li
|
|
4
|
+
v-if="item.items && item.text"
|
|
5
|
+
:class="{ active: isActive(item, route.path) }"
|
|
6
|
+
>
|
|
4
7
|
<details>
|
|
5
|
-
<summary>
|
|
8
|
+
<summary :class="{ active: isActive(item, route.path) }">
|
|
6
9
|
<span class="max" :class="{ active: isActive(item, route.path) }">{{
|
|
7
10
|
t(item.text, theme.i18n)
|
|
8
11
|
}}</span>
|
|
@@ -14,7 +17,7 @@
|
|
|
14
17
|
</details>
|
|
15
18
|
</li>
|
|
16
19
|
<MobileNavDropdown v-else-if="item.items" :items="item.items" />
|
|
17
|
-
<li v-else>
|
|
20
|
+
<li v-else :class="{ active: isActive(item, route.path) }">
|
|
18
21
|
<a
|
|
19
22
|
v-if="item.link"
|
|
20
23
|
:href="withBase(item.link)"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="top-nav at-top row center-align">
|
|
2
|
+
<div class="top-nav at-top row center-align" :class="{ 'has-hero': hasHero }">
|
|
3
3
|
<nav class="large-padding no-margin center-align nav-mobile">
|
|
4
4
|
<a :href="withBase('/')">
|
|
5
5
|
<img
|
|
@@ -33,10 +33,17 @@
|
|
|
33
33
|
</button>
|
|
34
34
|
</nav>
|
|
35
35
|
<ul class="list border large-space">
|
|
36
|
-
<li
|
|
36
|
+
<li
|
|
37
|
+
v-for="item in theme.nav.filter((item) => !item.action)"
|
|
38
|
+
:class="{ active: isActive(item, route.path) }"
|
|
39
|
+
>
|
|
37
40
|
<details v-if="item.items">
|
|
38
|
-
<summary>
|
|
39
|
-
<span
|
|
41
|
+
<summary :class="{ active: isActive(item, route.path) }">
|
|
42
|
+
<span
|
|
43
|
+
class="max"
|
|
44
|
+
:class="{ active: isActive(item, route.path) }"
|
|
45
|
+
>{{ t(item.text, theme.i18n) }}</span
|
|
46
|
+
>
|
|
40
47
|
<i class="mdi mdi-chevron-down"></i>
|
|
41
48
|
</summary>
|
|
42
49
|
<ul class="list">
|
|
@@ -49,7 +56,10 @@
|
|
|
49
56
|
:href="withBase(item.link)"
|
|
50
57
|
:target="item.target"
|
|
51
58
|
:rel="item.rel"
|
|
52
|
-
:class="
|
|
59
|
+
:class="[
|
|
60
|
+
item.action ? 'button large medium-elevate cta' : '',
|
|
61
|
+
{ active: isActive(item, route.path) },
|
|
62
|
+
]"
|
|
53
63
|
>
|
|
54
64
|
<span>{{ t(item.text, theme.i18n) }}</span>
|
|
55
65
|
</a>
|
|
@@ -135,7 +145,10 @@
|
|
|
135
145
|
<div class="space"></div>
|
|
136
146
|
<nav>
|
|
137
147
|
<ul class="left-align no-margin">
|
|
138
|
-
<li
|
|
148
|
+
<li
|
|
149
|
+
v-for="item in theme.nav.filter((i) => !i.action)"
|
|
150
|
+
:class="{ active: isActive(item, route.path) }"
|
|
151
|
+
>
|
|
139
152
|
<div v-if="item.items">
|
|
140
153
|
<button
|
|
141
154
|
class="button text no-round"
|
|
@@ -229,7 +242,7 @@ import { t, trackEvent, isActive } from "../helpers";
|
|
|
229
242
|
import NavDropdown from "./NavDropdown.vue";
|
|
230
243
|
import MobileNavDropdown from "./MobileNavDropdown.vue";
|
|
231
244
|
|
|
232
|
-
const { site, theme, localeIndex } = useData();
|
|
245
|
+
const { site, theme, localeIndex, frontmatter, page } = useData();
|
|
233
246
|
const route = useRoute();
|
|
234
247
|
|
|
235
248
|
const langs = computed(() => {
|
|
@@ -271,6 +284,14 @@ const langs = computed(() => {
|
|
|
271
284
|
});
|
|
272
285
|
});
|
|
273
286
|
|
|
287
|
+
const hasHero = computed(() => {
|
|
288
|
+
return Boolean(
|
|
289
|
+
frontmatter.value.hero ||
|
|
290
|
+
frontmatter.value.layout === "home" ||
|
|
291
|
+
page.value.relativePath === "index.md",
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
274
295
|
if (!import.meta.env.SSR) {
|
|
275
296
|
const scrollListener = () => {
|
|
276
297
|
const nav = document.querySelector(".top-nav");
|
|
@@ -301,7 +322,24 @@ if (!import.meta.env.SSR) {
|
|
|
301
322
|
.top-nav.at-top {
|
|
302
323
|
box-shadow: none;
|
|
303
324
|
}
|
|
304
|
-
.
|
|
325
|
+
.top-nav.at-top .nav-desktop .button.primary,
|
|
326
|
+
.top-nav.has-hero.at-top .nav-desktop .button.primary,
|
|
327
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button.primary {
|
|
328
|
+
background: #ffffff !important;
|
|
329
|
+
color: #000c14 !important;
|
|
330
|
+
border-radius: 9999px;
|
|
331
|
+
font-weight: 500;
|
|
332
|
+
display: inline-flex;
|
|
333
|
+
}
|
|
334
|
+
.top-nav.at-top .nav-desktop .button.primary i.mdi-arrow-right,
|
|
335
|
+
.top-nav.has-hero.at-top .nav-desktop .button.primary i.mdi-arrow-right,
|
|
336
|
+
.Layout.layout-home
|
|
337
|
+
.top-nav.at-top
|
|
338
|
+
.nav-desktop
|
|
339
|
+
.button.primary
|
|
340
|
+
i.mdi-arrow-right {
|
|
341
|
+
display: none;
|
|
342
|
+
}
|
|
305
343
|
.Layout.layout-home .top-nav.at-top .nav-desktop .button.secondary,
|
|
306
344
|
.Layout.layout-home .top-nav.at-top .nav-desktop .button.alt {
|
|
307
345
|
display: none;
|
|
@@ -317,43 +355,98 @@ if (!import.meta.env.SSR) {
|
|
|
317
355
|
padding-top: 0.5rem !important;
|
|
318
356
|
padding-bottom: 0.5rem !important;
|
|
319
357
|
}
|
|
320
|
-
.Layout.layout-home .top-nav.at-top
|
|
358
|
+
.Layout.layout-home .top-nav.at-top,
|
|
359
|
+
.top-nav.has-hero.at-top {
|
|
321
360
|
background: transparent;
|
|
322
361
|
color: #f7f8f8;
|
|
323
362
|
}
|
|
324
363
|
.Layout .top-nav nav .button {
|
|
325
364
|
color: var(--on-surface);
|
|
326
365
|
}
|
|
327
|
-
.Layout.layout-home .top-nav.at-top .nav-desktop .button
|
|
366
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button,
|
|
367
|
+
.top-nav.has-hero.at-top .nav-desktop .button {
|
|
328
368
|
color: var(--on-surface);
|
|
329
369
|
background: var(--surface);
|
|
330
370
|
}
|
|
331
|
-
.Layout.layout-home .top-nav.at-top .nav-desktop .button.text
|
|
371
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button.text,
|
|
372
|
+
.top-nav.has-hero.at-top .nav-desktop .button.text {
|
|
332
373
|
color: #f7f8f8;
|
|
333
374
|
background: none;
|
|
334
375
|
}
|
|
335
|
-
.Layout.layout-home
|
|
376
|
+
.Layout.layout-home
|
|
377
|
+
.top-nav.at-top
|
|
378
|
+
.nav-desktop
|
|
379
|
+
.button.text:hover:not(.active),
|
|
380
|
+
.top-nav.has-hero.at-top .nav-desktop .button.text:hover:not(.active) {
|
|
381
|
+
background: rgba(255, 255, 255, 0.1);
|
|
382
|
+
color: #ffffff;
|
|
383
|
+
}
|
|
384
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button.text.active,
|
|
385
|
+
.top-nav.has-hero.at-top .nav-desktop .button.text.active {
|
|
386
|
+
background-color: rgba(255, 255, 255, 0.15) !important;
|
|
387
|
+
color: #ffffff !important;
|
|
388
|
+
}
|
|
389
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button.text.active:hover,
|
|
390
|
+
.top-nav.has-hero.at-top .nav-desktop .button.text.active:hover {
|
|
391
|
+
background-color: rgba(255, 255, 255, 0.25) !important;
|
|
392
|
+
}
|
|
393
|
+
.top-nav:not(.at-top) .nav-desktop .button.text.active {
|
|
394
|
+
background-color: var(--primary-container) !important;
|
|
395
|
+
color: var(--on-primary-container) !important;
|
|
396
|
+
}
|
|
397
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button.border,
|
|
398
|
+
.top-nav.has-hero.at-top .nav-desktop .button.border {
|
|
336
399
|
color: #f7f8f8;
|
|
337
400
|
border-color: #f7f8f855;
|
|
338
401
|
background: none;
|
|
339
402
|
}
|
|
403
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button.border.active,
|
|
404
|
+
.top-nav.has-hero.at-top .nav-desktop .button.border.active {
|
|
405
|
+
background-color: rgba(255, 255, 255, 0.15) !important;
|
|
406
|
+
color: #ffffff !important;
|
|
407
|
+
border-color: transparent !important;
|
|
408
|
+
}
|
|
409
|
+
.top-nav:not(.at-top) .nav-desktop .button.border.active {
|
|
410
|
+
background-color: var(--primary-container) !important;
|
|
411
|
+
color: var(--on-primary-container) !important;
|
|
412
|
+
border-color: transparent !important;
|
|
413
|
+
}
|
|
414
|
+
.nav-desktop menu > li.active,
|
|
415
|
+
.nav-desktop menu > li:has(> a.active) {
|
|
416
|
+
background-color: var(--primary-container) !important;
|
|
417
|
+
color: var(--on-primary-container) !important;
|
|
418
|
+
}
|
|
419
|
+
.nav-desktop menu > li.active > a,
|
|
420
|
+
.nav-desktop menu > li:has(> a.active) > a {
|
|
421
|
+
color: var(--on-primary-container) !important;
|
|
422
|
+
}
|
|
423
|
+
#mobile-menu .list li.active > a,
|
|
424
|
+
#mobile-menu .list li.active > span,
|
|
425
|
+
#mobile-menu .list summary:has(.active),
|
|
426
|
+
#mobile-menu .list summary.active {
|
|
427
|
+
color: var(--primary) !important;
|
|
428
|
+
font-weight: 600;
|
|
429
|
+
}
|
|
340
430
|
.top-nav.at-top > nav > a:has(.logo.dark) > .logo.dark {
|
|
341
431
|
display: none;
|
|
342
432
|
}
|
|
343
|
-
.Layout.layout-home .top-nav.at-top > nav > a:has(.logo.dark) > .logo.light
|
|
433
|
+
.Layout.layout-home .top-nav.at-top > nav > a:has(.logo.dark) > .logo.light,
|
|
434
|
+
.top-nav.has-hero.at-top > nav > a:has(.logo.dark) > .logo.light {
|
|
344
435
|
display: none;
|
|
345
436
|
}
|
|
346
437
|
.Layout.layout-home
|
|
347
438
|
.top-nav.at-top
|
|
348
439
|
> nav
|
|
349
440
|
> a:not(:has(.logo.dark))
|
|
350
|
-
> .logo.light
|
|
441
|
+
> .logo.light,
|
|
442
|
+
.top-nav.has-hero.at-top > nav > a:not(:has(.logo.dark)) > .logo.light {
|
|
351
443
|
filter: brightness(0) invert(1);
|
|
352
444
|
}
|
|
353
445
|
.Layout .top-nav > nav > a > .logo.dark {
|
|
354
446
|
display: none;
|
|
355
447
|
}
|
|
356
|
-
.Layout.layout-home .top-nav.at-top > nav > a > .logo.dark
|
|
448
|
+
.Layout.layout-home .top-nav.at-top > nav > a > .logo.dark,
|
|
449
|
+
.top-nav.has-hero.at-top > nav > a > .logo.dark {
|
|
357
450
|
display: block;
|
|
358
451
|
}
|
|
359
452
|
img.logo {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-for="item in items">
|
|
3
|
-
<li
|
|
3
|
+
<li
|
|
4
|
+
v-if="item.items && item.text"
|
|
5
|
+
:class="{ active: isActive(item, route.path) }"
|
|
6
|
+
>
|
|
4
7
|
<a
|
|
5
8
|
v-if="item.link"
|
|
6
9
|
:href="withBase(item.link)"
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
</menu>
|
|
26
29
|
</li>
|
|
27
30
|
<NavDropdown v-else-if="item.items" :items="item.items" />
|
|
28
|
-
<li v-else>
|
|
31
|
+
<li v-else :class="{ active: isActive(item, route.path) }">
|
|
29
32
|
<a
|
|
30
33
|
v-if="item.link"
|
|
31
34
|
:href="withBase(item.link)"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tags-page">
|
|
3
|
-
|
|
2
|
+
<div class="tags-page" :class="{ 'has-hero': Boolean(frontmatter.hero) }">
|
|
3
|
+
<!-- Header (shown only when hero is not present) -->
|
|
4
|
+
<header v-if="!frontmatter.hero" class="tags-header">
|
|
4
5
|
<h1 class="tags-title">Blog Tags</h1>
|
|
5
6
|
<p class="tags-subtitle">
|
|
6
7
|
Explore our blog posts by topics and technology pillars.
|
|
@@ -69,10 +70,12 @@
|
|
|
69
70
|
|
|
70
71
|
<script setup>
|
|
71
72
|
import { computed } from "vue";
|
|
72
|
-
import { withBase } from "vitepress";
|
|
73
|
+
import { useData, withBase } from "vitepress";
|
|
73
74
|
import { data as posts } from "../posts.data.js";
|
|
74
75
|
import { getTagColor, getTagStyle } from "../tagUtils.js";
|
|
75
76
|
|
|
77
|
+
const { frontmatter } = useData();
|
|
78
|
+
|
|
76
79
|
const allTagsList = computed(() => {
|
|
77
80
|
// Aggregate all tag occurrences
|
|
78
81
|
const tagCounts = {};
|
|
@@ -111,6 +114,10 @@ const allTagsList = computed(() => {
|
|
|
111
114
|
box-sizing: border-box;
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
.tags-page.has-hero {
|
|
118
|
+
padding-top: 2rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
@media (min-width: 640px) {
|
|
115
122
|
.tags-page {
|
|
116
123
|
padding-left: 1.5rem;
|
|
@@ -118,6 +125,9 @@ const allTagsList = computed(() => {
|
|
|
118
125
|
padding-top: calc(var(--vp-nav-height) + 40px);
|
|
119
126
|
padding-bottom: 64px;
|
|
120
127
|
}
|
|
128
|
+
.tags-page.has-hero {
|
|
129
|
+
padding-top: 2.5rem;
|
|
130
|
+
}
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
@media (min-width: 960px) {
|
|
@@ -127,6 +137,9 @@ const allTagsList = computed(() => {
|
|
|
127
137
|
padding-top: calc(var(--vp-nav-height) + 58px);
|
|
128
138
|
padding-bottom: 96px;
|
|
129
139
|
}
|
|
140
|
+
.tags-page.has-hero {
|
|
141
|
+
padding-top: 3rem;
|
|
142
|
+
}
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
/* Page Header */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="team-page">
|
|
3
|
-
<!-- Header -->
|
|
4
|
-
<header class="team-header">
|
|
2
|
+
<div class="team-page" :class="{ 'has-hero': Boolean(frontmatter.hero) }">
|
|
3
|
+
<!-- Header (shown only when hero is not present) -->
|
|
4
|
+
<header v-if="!frontmatter.hero" class="team-header">
|
|
5
5
|
<h1 class="team-title">
|
|
6
6
|
{{ frontmatter.title || "Our Team" }}
|
|
7
7
|
</h1>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
/>
|
|
21
21
|
</section>
|
|
22
22
|
|
|
23
|
-
<!-- Team Grid -
|
|
23
|
+
<!-- Team Grid - up to 4 columns on large screens -->
|
|
24
24
|
<div class="team-grid">
|
|
25
25
|
<FeatureCard
|
|
26
26
|
v-for="member in sortedTeamMembers"
|
|
@@ -126,7 +126,7 @@ const memberHasPosts = (memberId) => {
|
|
|
126
126
|
<style scoped>
|
|
127
127
|
.team-page {
|
|
128
128
|
width: 100%;
|
|
129
|
-
max-width:
|
|
129
|
+
max-width: 1400px !important;
|
|
130
130
|
margin-left: auto !important;
|
|
131
131
|
margin-right: auto !important;
|
|
132
132
|
padding-left: 1rem !important;
|
|
@@ -137,6 +137,10 @@ const memberHasPosts = (memberId) => {
|
|
|
137
137
|
word-break: break-word;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
.team-page.has-hero {
|
|
141
|
+
padding-top: 2rem !important;
|
|
142
|
+
}
|
|
143
|
+
|
|
140
144
|
@media (min-width: 640px) {
|
|
141
145
|
.team-page {
|
|
142
146
|
padding-left: 1.5rem !important;
|
|
@@ -144,6 +148,9 @@ const memberHasPosts = (memberId) => {
|
|
|
144
148
|
padding-top: calc(var(--vp-nav-height) + 40px) !important;
|
|
145
149
|
padding-bottom: 64px !important;
|
|
146
150
|
}
|
|
151
|
+
.team-page.has-hero {
|
|
152
|
+
padding-top: 2.5rem !important;
|
|
153
|
+
}
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
@media (min-width: 960px) {
|
|
@@ -153,6 +160,9 @@ const memberHasPosts = (memberId) => {
|
|
|
153
160
|
padding-top: calc(var(--vp-nav-height) + 58px) !important;
|
|
154
161
|
padding-bottom: 96px !important;
|
|
155
162
|
}
|
|
163
|
+
.team-page.has-hero {
|
|
164
|
+
padding-top: 3rem !important;
|
|
165
|
+
}
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
/* Page Header */
|
|
@@ -235,6 +245,12 @@ const memberHasPosts = (memberId) => {
|
|
|
235
245
|
}
|
|
236
246
|
}
|
|
237
247
|
|
|
248
|
+
@media (min-width: 1280px) {
|
|
249
|
+
.team-grid {
|
|
250
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
238
254
|
/* Deep overrides to integrate with theme-provided FeatureCard */
|
|
239
255
|
.team-member-card {
|
|
240
256
|
height: 100% !important;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<section class="testimonials-section full-width surface-container-
|
|
2
|
+
<section class="testimonials-section full-width surface-container-lowest">
|
|
3
3
|
<div class="holder">
|
|
4
4
|
<!-- Left Side: Headline -->
|
|
5
5
|
<div class="headline">
|
|
@@ -9,10 +9,27 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<!-- Right Side: Text Block -->
|
|
12
|
-
<div
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<div
|
|
13
|
+
v-if="testimonials && testimonials.length > 0"
|
|
14
|
+
class="text-block"
|
|
15
|
+
role="region"
|
|
16
|
+
aria-roledescription="carousel"
|
|
17
|
+
:aria-label="title"
|
|
18
|
+
>
|
|
19
|
+
<!-- Testimonial Quotes Overlapping Grid -->
|
|
20
|
+
<div class="quotes-grid" aria-live="polite">
|
|
21
|
+
<div
|
|
22
|
+
v-for="(item, index) in testimonials"
|
|
23
|
+
:key="index"
|
|
24
|
+
class="quote-slide"
|
|
25
|
+
:class="{
|
|
26
|
+
active: index === currentIndex,
|
|
27
|
+
'quote-text': index === currentIndex,
|
|
28
|
+
}"
|
|
29
|
+
:aria-hidden="index !== currentIndex"
|
|
30
|
+
>
|
|
31
|
+
“{{ item.quote }}”
|
|
32
|
+
</div>
|
|
16
33
|
</div>
|
|
17
34
|
|
|
18
35
|
<!-- Divider / Separator -->
|
|
@@ -20,14 +37,28 @@
|
|
|
20
37
|
|
|
21
38
|
<!-- Frame 469: Author info & sliders -->
|
|
22
39
|
<div class="frame-469">
|
|
23
|
-
<!-- Frame 468: Author Name and Role -->
|
|
40
|
+
<!-- Frame 468: Author Name and Role Overlapping Grid -->
|
|
24
41
|
<div class="frame-468">
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{
|
|
30
|
-
|
|
42
|
+
<div
|
|
43
|
+
v-for="(item, index) in testimonials"
|
|
44
|
+
:key="index"
|
|
45
|
+
class="author-slide"
|
|
46
|
+
:class="{ active: index === currentIndex }"
|
|
47
|
+
:aria-hidden="index !== currentIndex"
|
|
48
|
+
>
|
|
49
|
+
<span
|
|
50
|
+
class="author-name-text"
|
|
51
|
+
:class="{ 'author-name': index === currentIndex }"
|
|
52
|
+
>
|
|
53
|
+
{{ item.author }}
|
|
54
|
+
</span>
|
|
55
|
+
<span
|
|
56
|
+
class="author-role-text"
|
|
57
|
+
:class="{ 'author-role': index === currentIndex }"
|
|
58
|
+
>
|
|
59
|
+
{{ item.role }}
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
31
62
|
</div>
|
|
32
63
|
|
|
33
64
|
<!-- sliders: Navigation Buttons -->
|
|
@@ -57,7 +88,7 @@
|
|
|
57
88
|
</template>
|
|
58
89
|
|
|
59
90
|
<script setup>
|
|
60
|
-
import { ref, computed } from "vue";
|
|
91
|
+
import { ref, computed, watch } from "vue";
|
|
61
92
|
|
|
62
93
|
const props = defineProps({
|
|
63
94
|
title: {
|
|
@@ -91,6 +122,16 @@ const props = defineProps({
|
|
|
91
122
|
|
|
92
123
|
const currentIndex = ref(0);
|
|
93
124
|
|
|
125
|
+
// Clamp currentIndex if testimonials prop array changes dynamically
|
|
126
|
+
watch(
|
|
127
|
+
() => props.testimonials?.length,
|
|
128
|
+
(len) => {
|
|
129
|
+
if (len && currentIndex.value >= len) {
|
|
130
|
+
currentIndex.value = 0;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
|
|
94
135
|
const currentTestimonial = computed(() => {
|
|
95
136
|
if (!props.testimonials || props.testimonials.length === 0) return null;
|
|
96
137
|
return props.testimonials[currentIndex.value];
|
|
@@ -118,6 +159,7 @@ section.testimonials-section {
|
|
|
118
159
|
margin-block-start: 6rem;
|
|
119
160
|
margin-block-end: 6rem;
|
|
120
161
|
box-sizing: border-box;
|
|
162
|
+
background-color: var(--surface-container-lowest, #ffffff);
|
|
121
163
|
}
|
|
122
164
|
|
|
123
165
|
@media (min-width: 1024px) {
|
|
@@ -168,6 +210,38 @@ section.testimonials-section {
|
|
|
168
210
|
gap: 24px;
|
|
169
211
|
}
|
|
170
212
|
|
|
213
|
+
.quotes-grid {
|
|
214
|
+
display: grid;
|
|
215
|
+
grid-template-columns: 1fr;
|
|
216
|
+
width: 100%;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.quote-slide {
|
|
220
|
+
grid-column: 1;
|
|
221
|
+
grid-row: 1;
|
|
222
|
+
align-self: start;
|
|
223
|
+
font-family: "Inter", sans-serif;
|
|
224
|
+
font-style: normal;
|
|
225
|
+
font-weight: 400;
|
|
226
|
+
font-size: 16px;
|
|
227
|
+
line-height: 24px;
|
|
228
|
+
letter-spacing: 0.5px;
|
|
229
|
+
color: #00060a;
|
|
230
|
+
min-height: 72px;
|
|
231
|
+
opacity: 0;
|
|
232
|
+
visibility: hidden;
|
|
233
|
+
transition:
|
|
234
|
+
opacity 0.25s ease,
|
|
235
|
+
visibility 0.25s ease;
|
|
236
|
+
pointer-events: none;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.quote-slide.active {
|
|
240
|
+
opacity: 1;
|
|
241
|
+
visibility: visible;
|
|
242
|
+
pointer-events: auto;
|
|
243
|
+
}
|
|
244
|
+
|
|
171
245
|
.quote-text {
|
|
172
246
|
font-family: "Inter", sans-serif;
|
|
173
247
|
font-style: normal;
|
|
@@ -195,14 +269,35 @@ section.testimonials-section {
|
|
|
195
269
|
}
|
|
196
270
|
|
|
197
271
|
.frame-468 {
|
|
272
|
+
display: grid;
|
|
273
|
+
grid-template-columns: 1fr;
|
|
274
|
+
align-items: flex-start;
|
|
275
|
+
flex-grow: 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.author-slide {
|
|
279
|
+
grid-column: 1;
|
|
280
|
+
grid-row: 1;
|
|
198
281
|
display: flex;
|
|
199
282
|
flex-direction: column;
|
|
200
283
|
align-items: flex-start;
|
|
201
284
|
gap: 8px;
|
|
202
|
-
|
|
285
|
+
opacity: 0;
|
|
286
|
+
visibility: hidden;
|
|
287
|
+
transition:
|
|
288
|
+
opacity 0.25s ease,
|
|
289
|
+
visibility 0.25s ease;
|
|
290
|
+
pointer-events: none;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.author-slide.active {
|
|
294
|
+
opacity: 1;
|
|
295
|
+
visibility: visible;
|
|
296
|
+
pointer-events: auto;
|
|
203
297
|
}
|
|
204
298
|
|
|
205
|
-
.author-name
|
|
299
|
+
.author-name,
|
|
300
|
+
.author-name-text {
|
|
206
301
|
font-family: "Inter", sans-serif;
|
|
207
302
|
font-style: normal;
|
|
208
303
|
font-weight: 700;
|
|
@@ -212,7 +307,8 @@ section.testimonials-section {
|
|
|
212
307
|
color: #00060a;
|
|
213
308
|
}
|
|
214
309
|
|
|
215
|
-
.author-role
|
|
310
|
+
.author-role,
|
|
311
|
+
.author-role-text {
|
|
216
312
|
font-family: "Inter", sans-serif;
|
|
217
313
|
font-style: normal;
|
|
218
314
|
font-weight: 400;
|
|
@@ -222,6 +318,13 @@ section.testimonials-section {
|
|
|
222
318
|
color: #00060a;
|
|
223
319
|
}
|
|
224
320
|
|
|
321
|
+
@media (prefers-reduced-motion: reduce) {
|
|
322
|
+
.quote-slide,
|
|
323
|
+
.author-slide {
|
|
324
|
+
transition: none;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
225
328
|
.sliders {
|
|
226
329
|
display: flex;
|
|
227
330
|
flex-direction: row;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import TextSection from "./TextSection.vue";
|
|
2
|
+
|
|
3
|
+
describe("<TextSection />", () => {
|
|
4
|
+
it("renders title, tagline and button", () => {
|
|
5
|
+
cy.mount(TextSection, {
|
|
6
|
+
props: {
|
|
7
|
+
title: "Test Section",
|
|
8
|
+
tagline: "Test Tagline",
|
|
9
|
+
buttonText: "Click Me",
|
|
10
|
+
buttonLink: "https://example.com",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
cy.contains("Test Section").should("exist");
|
|
15
|
+
cy.contains("Test Tagline").should("exist");
|
|
16
|
+
cy.contains("a.button", "Click Me")
|
|
17
|
+
.should("have.attr", "href", "https://example.com")
|
|
18
|
+
.and("have.attr", "target", "_blank");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders slot content and image", () => {
|
|
22
|
+
cy.mount(TextSection, {
|
|
23
|
+
props: {
|
|
24
|
+
image: "/test.jpg",
|
|
25
|
+
imageAlt: "Test Image",
|
|
26
|
+
},
|
|
27
|
+
slots: {
|
|
28
|
+
default: "<p>This is paragraph text.</p>",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
cy.contains("This is paragraph text.").should("exist");
|
|
33
|
+
cy.get("img.section-image")
|
|
34
|
+
.should("have.attr", "src", "/test.jpg")
|
|
35
|
+
.and("have.attr", "alt", "Test Image");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("applies reverse class when reverse prop is true", () => {
|
|
39
|
+
cy.mount(TextSection, {
|
|
40
|
+
props: {
|
|
41
|
+
image: "/test.jpg",
|
|
42
|
+
reverse: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
cy.get(".text-section").should("have.class", "is-reverse");
|
|
47
|
+
cy.get(".text-section-grid").should("have.class", "grid-reverse");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("applies single column styling when no image is provided", () => {
|
|
51
|
+
cy.mount(TextSection, {
|
|
52
|
+
slots: {
|
|
53
|
+
default: "<p>Single column text</p>",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
cy.get(".text-section").should("have.class", "no-image");
|
|
58
|
+
cy.get(".text-section-grid").should("have.class", "grid-single");
|
|
59
|
+
cy.get(".media-column").should("not.exist");
|
|
60
|
+
});
|
|
61
|
+
});
|