@eox/pages-theme-eox 2.0.0-alpha.1 → 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 (47) hide show
  1. package/.gitlab-ci.yml +4 -1
  2. package/README.md +75 -11
  3. package/cypress/support/mocks/authors.data.js +14 -0
  4. package/cypress/support/mocks/helpers.js +65 -4
  5. package/cypress.config.js +9 -0
  6. package/package.json +3 -1
  7. package/src/Layout.vue +14 -3
  8. package/src/components/BlogList.vue +74 -55
  9. package/src/components/BlogPost.vue +46 -24
  10. package/src/components/CTASection.vue +18 -4
  11. package/src/components/FeatureCard/Index.vue +113 -26
  12. package/src/components/FeatureCard/Poster.vue +25 -3
  13. package/src/components/FeatureSection.cy.js +16 -0
  14. package/src/components/FeatureSection.vue +3 -8
  15. package/src/components/FeaturesGallery.vue +34 -2
  16. package/src/components/Footer.cy.js +52 -2
  17. package/src/components/Footer.vue +17 -5
  18. package/src/components/HeroSection.cy.js +20 -0
  19. package/src/components/HeroSection.vue +139 -24
  20. package/src/components/ImageCarousel.vue +11 -2
  21. package/src/components/ImageElement.vue +16 -4
  22. package/src/components/ImageGallery.vue +29 -4
  23. package/src/components/LogoSection.vue +26 -3
  24. package/src/components/MobileNavDropdown.vue +13 -3
  25. package/src/components/NavBar.cy.js +15 -0
  26. package/src/components/NavBar.vue +149 -21
  27. package/src/components/NavDropdown.vue +12 -2
  28. package/src/components/OpenPositions.vue +292 -0
  29. package/src/components/TagList.vue +32 -14
  30. package/src/components/TeamPage.vue +34 -20
  31. package/src/components/TestimonialsSection.cy.js +2 -5
  32. package/src/components/TestimonialsSection.vue +119 -16
  33. package/src/components/TextSection.cy.js +115 -0
  34. package/src/components/TextSection.vue +396 -0
  35. package/src/helpers.js +182 -37
  36. package/src/helpers.test.js +181 -0
  37. package/src/index.js +4 -0
  38. package/src/pages/author/[author].md +1 -0
  39. package/src/pages/author/[author].paths.js +2 -2
  40. package/src/pages/blog.md +1 -0
  41. package/src/pages/tag/[tag].md +1 -0
  42. package/src/pages/tag.md +1 -0
  43. package/src/pages/team.md +2 -1
  44. package/src/posts.data.js +39 -1
  45. package/src/style.css +232 -11
  46. package/src/tagUtils.js +5 -5
  47. package/src/vitepressConfig.mjs +6 -3
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section class="testimonials-section full-width surface-container-low">
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 v-if="testimonials && testimonials.length > 0" class="text-block">
13
- <!-- Testimonial Quote -->
14
- <div class="quote-text">
15
- &ldquo;{{ currentTestimonial.quote }}&rdquo;
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
+ &ldquo;{{ item.quote }}&rdquo;
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
- <span class="author-name">
26
- {{ currentTestimonial.author }}
27
- </span>
28
- <span class="author-role">
29
- {{ currentTestimonial.role }}
30
- </span>
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
- flex-grow: 1;
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,115 @@
1
+ import { h } from "vue";
2
+ import TextSection from "./TextSection.vue";
3
+
4
+ describe("<TextSection />", () => {
5
+ it("renders title, tagline and button", () => {
6
+ cy.mount(TextSection, {
7
+ props: {
8
+ title: "Test Section",
9
+ tagline: "Test Tagline",
10
+ buttonText: "Click Me",
11
+ buttonLink: "https://example.com",
12
+ },
13
+ });
14
+
15
+ cy.contains("Test Section").should("exist");
16
+ cy.contains("Test Tagline").should("exist");
17
+ cy.contains("a.button", "Click Me")
18
+ .should("have.attr", "href", "https://example.com")
19
+ .and("have.attr", "target", "_blank");
20
+ });
21
+
22
+ it("renders slot content and image", () => {
23
+ cy.mount(TextSection, {
24
+ props: {
25
+ image: "/test.jpg",
26
+ imageAlt: "Test Image",
27
+ },
28
+ slots: {
29
+ default: "<p>This is paragraph text.</p>",
30
+ },
31
+ });
32
+
33
+ cy.contains("This is paragraph text.").should("exist");
34
+ cy.get("img.section-image")
35
+ .should("have.attr", "src", "/test.jpg")
36
+ .and("have.attr", "alt", "Test Image");
37
+ });
38
+
39
+ it("applies reverse class when reverse prop is true", () => {
40
+ cy.mount(TextSection, {
41
+ props: {
42
+ image: "/test.jpg",
43
+ reverse: true,
44
+ },
45
+ });
46
+
47
+ cy.get(".text-section").should("have.class", "is-reverse");
48
+ cy.get(".text-section-grid").should("have.class", "grid-reverse");
49
+ });
50
+
51
+ it("applies single column styling when no image is provided", () => {
52
+ cy.mount(TextSection, {
53
+ slots: {
54
+ default: "<p>Single column text</p>",
55
+ },
56
+ });
57
+
58
+ cy.get(".text-section").should("have.class", "no-image");
59
+ cy.get(".text-section-grid").should("have.class", "grid-single");
60
+ cy.get(".media-column").should("not.exist");
61
+ });
62
+
63
+ it("applies extra padding-block-end to a single section", () => {
64
+ cy.mount(TextSection, {
65
+ props: {
66
+ title: "Single Section",
67
+ },
68
+ });
69
+
70
+ cy.get(".text-section").should("have.css", "padding-bottom", "80px");
71
+ cy.get(".text-section").should("have.css", "padding-top", "56px");
72
+ });
73
+
74
+ it("applies extra padding-block-end only to the last section when multiple are consecutive", () => {
75
+ const TestWrapper = {
76
+ render() {
77
+ return h("div", [
78
+ h(TextSection, { title: "Section 1" }),
79
+ h(TextSection, { title: "Section 2" }),
80
+ h(TextSection, { title: "Section 3" }),
81
+ ]);
82
+ },
83
+ };
84
+
85
+ cy.mount(TestWrapper);
86
+
87
+ // First section: standard padding-bottom 56px, normal padding-top 56px
88
+ cy.get(".text-section").eq(0).should("have.css", "padding-bottom", "56px");
89
+ cy.get(".text-section").eq(0).should("have.css", "padding-top", "56px");
90
+
91
+ // Second section: standard padding-bottom 56px, tightened padding-top 16px
92
+ cy.get(".text-section").eq(1).should("have.css", "padding-bottom", "56px");
93
+ cy.get(".text-section").eq(1).should("have.css", "padding-top", "16px");
94
+
95
+ // Third (last) section: extra padding-bottom 80px, tightened padding-top 16px
96
+ cy.get(".text-section").eq(2).should("have.css", "padding-bottom", "80px");
97
+ cy.get(".text-section").eq(2).should("have.css", "padding-top", "16px");
98
+ });
99
+
100
+ it("handles compact sections with appropriate padding-block-end", () => {
101
+ const TestWrapper = {
102
+ render() {
103
+ return h("div", [
104
+ h(TextSection, { title: "Compact 1", compact: true }),
105
+ h(TextSection, { title: "Compact 2", compact: true }),
106
+ ]);
107
+ },
108
+ };
109
+
110
+ cy.mount(TestWrapper);
111
+
112
+ cy.get(".text-section").eq(0).should("have.css", "padding-bottom", "28px");
113
+ cy.get(".text-section").eq(1).should("have.css", "padding-bottom", "40px");
114
+ });
115
+ });
@@ -0,0 +1,396 @@
1
+ <template>
2
+ <section
3
+ class="text-section full-width"
4
+ :class="{
5
+ 'is-reverse': reverse,
6
+ 'no-image': !image && !$slots.media,
7
+ 'is-compact': compact,
8
+ }"
9
+ >
10
+ <div class="holder">
11
+ <div
12
+ class="text-section-grid"
13
+ :class="{
14
+ 'grid-reverse': reverse,
15
+ 'grid-single': !image && !$slots.media,
16
+ }"
17
+ >
18
+ <!-- Text Column -->
19
+ <div class="text-column">
20
+ <span v-if="tagline" class="tagline">{{ tagline }}</span>
21
+ <h2 v-if="title" class="section-title">{{ title }}</h2>
22
+ <div class="section-content">
23
+ <slot></slot>
24
+ </div>
25
+ <!-- Prop-based button -->
26
+ <div v-if="buttonText && buttonLink" class="section-actions">
27
+ <a
28
+ :href="resolvedButtonLink"
29
+ :target="resolvedButtonTarget"
30
+ class="button"
31
+ @click="
32
+ trackEvent([
33
+ 'TextSection',
34
+ 'Click',
35
+ title || buttonText,
36
+ buttonText,
37
+ ])
38
+ "
39
+ >
40
+ <span>{{ buttonText }}</span>
41
+ <i v-if="buttonIcon" :class="buttonIcon" class="btn-icon"></i>
42
+ </a>
43
+ </div>
44
+ <slot name="actions"></slot>
45
+ </div>
46
+
47
+ <!-- Media Column -->
48
+ <div v-if="image || $slots.media" class="media-column">
49
+ <slot name="media">
50
+ <img
51
+ v-if="image"
52
+ :src="resolvedImage"
53
+ :alt="imageAlt || title || ''"
54
+ class="section-image"
55
+ loading="lazy"
56
+ />
57
+ </slot>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </section>
62
+ </template>
63
+
64
+ <script setup>
65
+ import { withBase } from "vitepress";
66
+ import { computed } from "vue";
67
+ import { trackEvent } from "../helpers.js";
68
+
69
+ const props = defineProps({
70
+ title: {
71
+ type: String,
72
+ default: "",
73
+ },
74
+ tagline: {
75
+ type: String,
76
+ default: "",
77
+ },
78
+ image: {
79
+ type: String,
80
+ default: "",
81
+ },
82
+ imageAlt: {
83
+ type: String,
84
+ default: "",
85
+ },
86
+ reverse: {
87
+ type: Boolean,
88
+ default: false,
89
+ },
90
+ buttonText: {
91
+ type: String,
92
+ default: "",
93
+ },
94
+ buttonLink: {
95
+ type: String,
96
+ default: "",
97
+ },
98
+ buttonTarget: {
99
+ type: String,
100
+ default: "",
101
+ },
102
+ buttonIcon: {
103
+ type: String,
104
+ default: "",
105
+ },
106
+ compact: {
107
+ type: Boolean,
108
+ default: false,
109
+ },
110
+ });
111
+
112
+ function resolveUrl(url) {
113
+ if (!url) return "";
114
+ if (
115
+ url.startsWith("http://") ||
116
+ url.startsWith("https://") ||
117
+ url.startsWith("//") ||
118
+ url.startsWith("#")
119
+ ) {
120
+ return url;
121
+ }
122
+ return withBase(url);
123
+ }
124
+
125
+ const resolvedImage = computed(() => resolveUrl(props.image));
126
+ const resolvedButtonLink = computed(() => resolveUrl(props.buttonLink));
127
+ const resolvedButtonTarget = computed(() => {
128
+ if (props.buttonTarget) return props.buttonTarget;
129
+ if (
130
+ props.buttonLink &&
131
+ (props.buttonLink.startsWith("http://") ||
132
+ props.buttonLink.startsWith("https://") ||
133
+ props.buttonLink.startsWith("//"))
134
+ ) {
135
+ return "_blank";
136
+ }
137
+ return "_self";
138
+ });
139
+ </script>
140
+
141
+ <style scoped>
142
+ .full-width {
143
+ width: 100svw;
144
+ position: relative;
145
+ left: 50%;
146
+ right: 50%;
147
+ margin-left: -50svw;
148
+ margin-right: -50svw;
149
+ max-inline-size: unset !important;
150
+ }
151
+
152
+ .text-section {
153
+ padding-block-start: 56px;
154
+ padding-block-end: 80px;
155
+ display: flex;
156
+ justify-content: center;
157
+ background: transparent;
158
+ color: var(--on-surface, #020617);
159
+ }
160
+
161
+ .text-section.is-compact {
162
+ padding-block-start: 28px;
163
+ padding-block-end: 40px;
164
+ }
165
+
166
+ /* When text-sections are placed consecutively, tighten spacing and only apply extra end padding to the last one */
167
+ :deep(.text-section:has(+ .text-section)),
168
+ .text-section:has(+ .text-section) {
169
+ padding-block-end: 56px;
170
+ }
171
+
172
+ :deep(.text-section.is-compact:has(+ .text-section)),
173
+ .text-section.is-compact:has(+ .text-section) {
174
+ padding-block-end: 28px;
175
+ }
176
+
177
+ :deep(.text-section + .text-section),
178
+ .text-section + .text-section {
179
+ padding-block-start: 16px;
180
+ }
181
+
182
+ .text-section .holder {
183
+ width: 100%;
184
+ max-width: 1200px;
185
+ margin-left: auto;
186
+ margin-right: auto;
187
+ padding-inline: 24px;
188
+ }
189
+
190
+ .text-section-grid {
191
+ display: grid;
192
+ grid-template-columns: 1fr;
193
+ gap: 2rem;
194
+ align-items: center;
195
+ }
196
+
197
+ /* On mobile (< 1024px), the media/image column is always at the start before the text */
198
+ .text-section-grid .media-column {
199
+ order: 1;
200
+ }
201
+
202
+ .text-section-grid .text-column {
203
+ order: 2;
204
+ }
205
+
206
+ @media (min-width: 1024px) {
207
+ .text-section-grid {
208
+ grid-template-columns: 1fr 1fr;
209
+ gap: 4rem;
210
+ }
211
+
212
+ /* Default on desktop: Text on left (1), Media on right (2) */
213
+ .text-section-grid .text-column {
214
+ order: 1;
215
+ }
216
+
217
+ .text-section-grid .media-column {
218
+ order: 2;
219
+ }
220
+
221
+ /* Reverse on desktop: Media on left (1), Text on right (2) */
222
+ .text-section-grid.grid-reverse .text-column {
223
+ order: 2;
224
+ }
225
+
226
+ .text-section-grid.grid-reverse .media-column {
227
+ order: 1;
228
+ }
229
+
230
+ .text-section-grid.grid-single {
231
+ grid-template-columns: 1fr;
232
+ max-width: 680px;
233
+ }
234
+ }
235
+
236
+ .text-column {
237
+ display: flex;
238
+ flex-direction: column;
239
+ width: 100%;
240
+ }
241
+
242
+ .tagline {
243
+ display: inline-block;
244
+ font-size: 0.875rem;
245
+ font-weight: 600;
246
+ text-transform: uppercase;
247
+ letter-spacing: 0.05em;
248
+ color: var(--primary, #004170);
249
+ margin-bottom: 0.5rem;
250
+ }
251
+
252
+ .section-title {
253
+ font-family: inherit;
254
+ font-weight: 700;
255
+ font-size: 2rem;
256
+ line-height: 1.25;
257
+ color: #020617;
258
+ margin-top: 0;
259
+ margin-bottom: 1.25rem;
260
+ border-top: none;
261
+ }
262
+
263
+ /* Markdown Content Styling */
264
+ .section-content :deep(h2) {
265
+ font-family: inherit;
266
+ font-weight: 700;
267
+ font-size: 2rem;
268
+ line-height: 1.25;
269
+ color: #020617;
270
+ margin-top: 0;
271
+ margin-bottom: 1.25rem;
272
+ border-top: none;
273
+ }
274
+
275
+ .section-content :deep(h3) {
276
+ font-family: inherit;
277
+ font-weight: 700;
278
+ font-size: 1.5rem;
279
+ line-height: 1.35;
280
+ color: #020617;
281
+ margin-top: 1.5rem;
282
+ margin-bottom: 0.75rem;
283
+ border-top: none;
284
+ }
285
+
286
+ .section-content :deep(h4),
287
+ .section-content :deep(h5) {
288
+ font-family: inherit;
289
+ font-weight: 600;
290
+ font-size: 1.125rem;
291
+ line-height: 1.4;
292
+ color: #020617;
293
+ margin-top: 1.25rem;
294
+ margin-bottom: 0.5rem;
295
+ }
296
+
297
+ .section-content :deep(p) {
298
+ font-size: 1rem;
299
+ line-height: 1.65;
300
+ color: #334155;
301
+ margin-top: 0;
302
+ margin-bottom: 1rem;
303
+ }
304
+
305
+ .section-content :deep(ul) {
306
+ margin-top: 0.75rem;
307
+ margin-bottom: 1.25rem;
308
+ padding-left: 1.25rem;
309
+ list-style-type: disc;
310
+ }
311
+
312
+ .section-content :deep(li) {
313
+ font-size: 0.95rem;
314
+ line-height: 1.6;
315
+ color: #334155;
316
+ margin-bottom: 0.5rem;
317
+ }
318
+
319
+ .section-content :deep(li strong) {
320
+ color: #020617;
321
+ font-weight: 700;
322
+ }
323
+
324
+ .section-content :deep(a:not(.button)) {
325
+ color: #0284c7;
326
+ text-decoration: none;
327
+ font-weight: 500;
328
+ transition: color 0.15s ease-in-out;
329
+ }
330
+
331
+ .section-content :deep(a:not(.button):hover) {
332
+ text-decoration: underline;
333
+ color: #0369a1;
334
+ }
335
+
336
+ /* Pill Button Styling - for both prop and markdown links with .button */
337
+ .section-actions {
338
+ margin-top: 0.75rem;
339
+ margin-bottom: 0.5rem;
340
+ }
341
+
342
+ .button,
343
+ .section-content :deep(a.button),
344
+ .section-content :deep(.button) {
345
+ display: inline-flex;
346
+ align-items: center;
347
+ justify-content: center;
348
+ gap: 0.5rem;
349
+ padding: 0.5rem 1.5rem;
350
+ border-radius: 9999px;
351
+ border: 1px solid #020617;
352
+ background-color: #ffffff;
353
+ color: #020617 !important;
354
+ font-weight: 600;
355
+ font-size: 0.9375rem;
356
+ line-height: 1.5;
357
+ text-decoration: none !important;
358
+ transition: all 0.2s ease-in-out;
359
+ cursor: pointer;
360
+ width: fit-content;
361
+ }
362
+
363
+ .button:hover,
364
+ .section-content :deep(a.button:hover),
365
+ .section-content :deep(.button:hover) {
366
+ background-color: #f1f5f9;
367
+ border-color: #020617;
368
+ color: #020617 !important;
369
+ }
370
+
371
+ .btn-icon {
372
+ font-size: 1.1em;
373
+ display: inline-flex;
374
+ }
375
+
376
+ /* Media Column & Image Styling */
377
+ .media-column {
378
+ display: flex;
379
+ justify-content: center;
380
+ align-items: center;
381
+ width: 100%;
382
+ }
383
+
384
+ .section-image {
385
+ width: 100%;
386
+ max-width: 100%;
387
+ height: auto;
388
+ border-radius: 16px;
389
+ border: 1px solid rgba(15, 23, 42, 0.08);
390
+ box-shadow:
391
+ 0 4px 16px -2px rgba(15, 23, 42, 0.06),
392
+ 0 2px 6px -2px rgba(15, 23, 42, 0.04);
393
+ background-color: #ffffff;
394
+ display: block;
395
+ }
396
+ </style>