@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
@@ -0,0 +1,311 @@
1
+ <template>
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">
5
+ <h1 class="tags-title">Blog Tags</h1>
6
+ <p class="tags-subtitle">
7
+ Explore our blog posts by topics and technology pillars.
8
+ </p>
9
+ </header>
10
+
11
+ <!-- Unified flat list of all tags -->
12
+ <div class="tags-container">
13
+ <div class="tags-flex">
14
+ <a
15
+ v-for="tag in allTagsList"
16
+ :key="tag.name"
17
+ :href="withBase(`/tag/${tag.name}/`)"
18
+ :style="getTagStyle(tag.name)"
19
+ class="tag-item"
20
+ >
21
+ <span>{{ tag.name }}</span>
22
+ <span class="tag-count">{{ tag.count }}</span>
23
+ </a>
24
+ </div>
25
+ </div>
26
+
27
+ <!-- Tech pillars legend overview footer -->
28
+ <footer class="tags-footer">
29
+ <h3 class="legend-title">Technology Pillars:</h3>
30
+ <ul class="legend-grid">
31
+ <li class="legend-item">
32
+ <span
33
+ class="legend-dot"
34
+ style="background-color: rgb(255, 94, 94)"
35
+ ></span>
36
+ <span>Managed cloud workspace</span>
37
+ </li>
38
+ <li class="legend-item">
39
+ <span
40
+ class="legend-dot"
41
+ style="background-color: rgb(0, 177, 176)"
42
+ ></span>
43
+ <span>Data management</span>
44
+ </li>
45
+ <li class="legend-item">
46
+ <span
47
+ class="legend-dot"
48
+ style="background-color: rgb(219, 173, 66)"
49
+ ></span>
50
+ <span>Data visualization</span>
51
+ </li>
52
+ <li class="legend-item">
53
+ <span
54
+ class="legend-dot"
55
+ style="background-color: rgb(218, 71, 228)"
56
+ ></span>
57
+ <span>Data products</span>
58
+ </li>
59
+ </ul>
60
+ <p class="legend-note">
61
+ For more detailed information on our technology solutions, visit the
62
+ <a :href="withBase('/solutions')" class="legend-link"
63
+ >Solutions & Services</a
64
+ >
65
+ page.
66
+ </p>
67
+ </footer>
68
+ </div>
69
+ </template>
70
+
71
+ <script setup>
72
+ import { computed } from "vue";
73
+ import { useData, withBase } from "vitepress";
74
+ import { data as posts } from "../posts.data.js";
75
+ import { getTagColor, getTagStyle } from "../tagUtils.js";
76
+
77
+ const { frontmatter } = useData();
78
+
79
+ const allTagsList = computed(() => {
80
+ // Aggregate all tag occurrences
81
+ const tagCounts = {};
82
+ posts.forEach((p) => {
83
+ if (Array.isArray(p.tags)) {
84
+ p.tags.forEach((t) => {
85
+ const clean = t.trim().toLowerCase();
86
+ if (clean) {
87
+ tagCounts[clean] = (tagCounts[clean] || 0) + 1;
88
+ }
89
+ });
90
+ }
91
+ });
92
+
93
+ // Convert to sorted list
94
+ return Object.entries(tagCounts)
95
+ .map(([name, count]) => ({
96
+ name,
97
+ count,
98
+ color: getTagColor(name),
99
+ }))
100
+ .sort((a, b) => a.name.localeCompare(b.name));
101
+ });
102
+ </script>
103
+
104
+ <style scoped>
105
+ .tags-page {
106
+ width: 100%;
107
+ max-width: 1200px;
108
+ margin-left: auto;
109
+ margin-right: auto;
110
+ padding-left: 1rem;
111
+ padding-right: 1rem;
112
+ padding-top: calc(var(--vp-nav-height) + 24px);
113
+ padding-bottom: 48px;
114
+ box-sizing: border-box;
115
+ }
116
+
117
+ .tags-page.has-hero {
118
+ padding-top: 2rem;
119
+ }
120
+
121
+ @media (min-width: 640px) {
122
+ .tags-page {
123
+ padding-left: 1.5rem;
124
+ padding-right: 1.5rem;
125
+ padding-top: calc(var(--vp-nav-height) + 40px);
126
+ padding-bottom: 64px;
127
+ }
128
+ .tags-page.has-hero {
129
+ padding-top: 2.5rem;
130
+ }
131
+ }
132
+
133
+ @media (min-width: 960px) {
134
+ .tags-page {
135
+ padding-left: 2rem;
136
+ padding-right: 2rem;
137
+ padding-top: calc(var(--vp-nav-height) + 58px);
138
+ padding-bottom: 96px;
139
+ }
140
+ .tags-page.has-hero {
141
+ padding-top: 3rem;
142
+ }
143
+ }
144
+
145
+ /* Page Header */
146
+ .tags-header {
147
+ margin-bottom: 2rem;
148
+ border-bottom: 1px solid #f1f5f9; /* slate-100 */
149
+ padding-bottom: 1.5rem;
150
+ text-align: left;
151
+ }
152
+
153
+ .tags-title {
154
+ font-family: "Inter", sans-serif;
155
+ font-style: normal;
156
+ font-weight: 700;
157
+ font-size: 28px;
158
+ line-height: 36px;
159
+ color: #020617; /* slate-950 */
160
+ margin-top: 0;
161
+ margin-bottom: 0.5rem;
162
+ letter-spacing: 0.5px;
163
+ }
164
+
165
+ @media (min-width: 768px) {
166
+ .tags-title {
167
+ font-size: 32px;
168
+ line-height: 40px;
169
+ }
170
+ }
171
+
172
+ .tags-subtitle {
173
+ font-family: "Inter", sans-serif;
174
+ font-style: normal;
175
+ font-weight: 400;
176
+ font-size: 16px;
177
+ line-height: 24px;
178
+ color: #64748b; /* slate-500 */
179
+ max-width: 620px;
180
+ margin-left: auto;
181
+ margin-right: auto;
182
+ margin-top: 0;
183
+ margin-bottom: 0;
184
+ padding: 0;
185
+ letter-spacing: 0.5px;
186
+ }
187
+
188
+ /* Tag Collection Container */
189
+ .tags-container {
190
+ background-color: var(--surface-container-low);
191
+ padding: 2.5rem 2rem;
192
+ border-radius: 1rem;
193
+ border: 1px solid rgba(226, 232, 240, 0.5); /* border-slate-100/50 */
194
+ box-shadow:
195
+ 0 1px 3px 0 rgba(0, 0, 0, 0.05),
196
+ 0 1px 2px -1px rgba(0, 0, 0, 0.05); /* shadow-sm */
197
+ margin-bottom: 3rem;
198
+ }
199
+
200
+ .tags-flex {
201
+ display: flex;
202
+ flex-wrap: wrap;
203
+ gap: 0.75rem;
204
+ justify-content: center;
205
+ }
206
+
207
+ /* Tag Item Style */
208
+ .tag-item {
209
+ display: inline-flex;
210
+ align-items: center;
211
+ gap: 0.5rem;
212
+ font-size: 0.875rem; /* text-sm */
213
+ font-weight: 600;
214
+ padding: 0.375rem 0.875rem;
215
+ border-radius: 4px; /* small rounding to distinguish from full-rounded buttons */
216
+ text-decoration: none !important;
217
+ line-height: 1.2;
218
+ transition:
219
+ opacity 0.2s ease,
220
+ transform 0.15s ease,
221
+ box-shadow 0.2s ease;
222
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
223
+ }
224
+
225
+ .tag-item:hover {
226
+ opacity: 0.85;
227
+ transform: translateY(-1px);
228
+ box-shadow:
229
+ 0 4px 6px -1px rgba(0, 0, 0, 0.05),
230
+ 0 2px 4px -2px rgba(0, 0, 0, 0.05);
231
+ }
232
+
233
+ .tag-count {
234
+ font-size: 0.75rem;
235
+ padding: 0.125rem 0.375rem;
236
+ background-color: rgba(255, 255, 255, 0.4);
237
+ color: inherit;
238
+ border-radius: 9999px;
239
+ font-weight: 700;
240
+ }
241
+
242
+ /* Legend Overview Footer */
243
+ .tags-footer {
244
+ background-color: var(--surface-container-low);
245
+ padding: 2rem;
246
+ border-radius: 1rem;
247
+ border: 1px solid rgba(226, 232, 240, 0.5);
248
+ max-width: 48rem; /* 3xl equivalent */
249
+ margin-left: auto;
250
+ margin-right: auto;
251
+ margin-top: 4rem;
252
+ }
253
+
254
+ .legend-title {
255
+ font-family: "Inter", sans-serif;
256
+ font-size: 1.125rem; /* text-lg */
257
+ font-weight: 700;
258
+ color: #020617; /* slate-950 */
259
+ margin-top: 0;
260
+ margin-bottom: 1rem;
261
+ }
262
+
263
+ .legend-grid {
264
+ display: grid;
265
+ grid-template-columns: 1fr;
266
+ gap: 1rem;
267
+ padding-left: 0;
268
+ list-style: none;
269
+ margin: 0;
270
+ }
271
+
272
+ @media (min-width: 640px) {
273
+ .legend-grid {
274
+ grid-template-columns: repeat(2, minmax(0, 1fr));
275
+ }
276
+ }
277
+
278
+ .legend-item {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 0.625rem;
282
+ font-size: 0.875rem; /* text-sm */
283
+ color: #475569; /* slate-600 */
284
+ }
285
+
286
+ .legend-dot {
287
+ width: 0.875rem;
288
+ height: 0.875rem;
289
+ border-radius: 9999px;
290
+ flex-shrink: 0;
291
+ }
292
+
293
+ .legend-note {
294
+ font-size: 0.875rem;
295
+ color: #64748b; /* slate-500 */
296
+ margin-top: 1.5rem;
297
+ padding-top: 1rem;
298
+ border-top: 1px solid rgba(226, 232, 240, 0.6);
299
+ line-height: 1.5;
300
+ }
301
+
302
+ .legend-link {
303
+ color: var(--primary, #004170);
304
+ font-weight: 600;
305
+ text-decoration: none;
306
+ }
307
+
308
+ .legend-link:hover {
309
+ text-decoration: underline;
310
+ }
311
+ </style>
@@ -0,0 +1,347 @@
1
+ <template>
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
+ <h1 class="team-title">
6
+ {{ frontmatter.title || "Our Team" }}
7
+ </h1>
8
+ <p class="team-subtitle">
9
+ Meet the diverse team of geospatial experts, engineers, and visionaries
10
+ driving EOX forward.
11
+ </p>
12
+ </header>
13
+
14
+ <!-- Team Photo Section -->
15
+ <section class="team-photo-section">
16
+ <img
17
+ :src="withBase('/images/team/team.jpg')"
18
+ alt="Our Team Photo"
19
+ class="team-photo"
20
+ />
21
+ </section>
22
+
23
+ <!-- Team Grid - up to 4 columns on large screens -->
24
+ <div class="team-grid">
25
+ <FeatureCard
26
+ v-for="member in sortedTeamMembers"
27
+ :key="member.id"
28
+ :title="member.name"
29
+ :image="
30
+ member.image
31
+ ? withBase(member.image)
32
+ : withBase('/images/team/new_member.jpg')
33
+ "
34
+ :link="
35
+ memberHasPosts(member.id)
36
+ ? {
37
+ text: 'View blog posts',
38
+ href: `/author/${member.id}/`,
39
+ target: '_self',
40
+ }
41
+ : undefined
42
+ "
43
+ variant="cover"
44
+ class="bg-surface-container-low team-member-card"
45
+ >
46
+ <!-- Role / Job Title -->
47
+ <span v-if="member.role" class="member-role">
48
+ {{ member.role }}
49
+ </span>
50
+
51
+ <span class="member-divider"></span>
52
+
53
+ <!-- Description / Bio -->
54
+ <span v-if="member.bio" class="member-bio" v-html="member.bio"></span>
55
+
56
+ <!-- Social Links row at the bottom of slot -->
57
+ <span class="member-footer">
58
+ <!-- Social Icons - aligned to the left matching other content -->
59
+ <span class="social-links-row">
60
+ <template v-for="(url, platform) in member.sns" :key="platform">
61
+ <a
62
+ v-if="url"
63
+ :href="url"
64
+ target="_blank"
65
+ rel="noopener noreferrer"
66
+ class="social-link-btn"
67
+ :title="`${member.name} on ${platform}`"
68
+ >
69
+ <!-- Bluesky custom SVG -->
70
+ <svg
71
+ v-if="platform === 'bluesky'"
72
+ viewBox="0 0 512 512"
73
+ style="width: 18px; height: 18px; fill: currentColor"
74
+ >
75
+ <path
76
+ d="M111.8 109.6C166.1 154.2 226.8 231.1 256 271.7c29.2-40.6 89.9-117.5 144.2-162.1C443.3 74.3 512 57.4 512 121.7c0 23.3-12.7 101.4-19 123.5-16 56.6-70.3 70.8-111.8 62.4C443 328.1 512 361.3 512 411.1c0 51.5-68.7 65.1-111.8 18.2-70.3 76.5-161.4 11-200.2-29-38.8 40-129.9 105.5-200.2 29C56.7 476.2 0 462.6 0 411.1c0-49.8 69-83 130.8-103.5C89.3 316 35 301.8 19 245.2 12.7 223 0 145 0 121.7c0-64.3 68.7-47.4 111.8-12.1z"
77
+ />
78
+ </svg>
79
+ <i
80
+ v-else-if="platform === 'linkedin'"
81
+ class="mdi mdi-linkedin text-lg"
82
+ ></i>
83
+ <i
84
+ v-else-if="platform === 'github'"
85
+ class="mdi mdi-github text-lg"
86
+ ></i>
87
+ <i
88
+ v-else-if="platform === 'twitter'"
89
+ class="mdi mdi-twitter text-lg"
90
+ ></i>
91
+ <i
92
+ v-else-if="platform === 'facebook'"
93
+ class="mdi mdi-facebook text-lg"
94
+ ></i>
95
+ <i v-else :class="`mdi mdi-${platform} text-lg`"></i>
96
+ </a>
97
+ </template>
98
+ </span>
99
+ </span>
100
+ </FeatureCard>
101
+ </div>
102
+ </div>
103
+ </template>
104
+
105
+ <script setup>
106
+ import { computed } from "vue";
107
+ import { useData, withBase } from "vitepress";
108
+ import { data as teamMembers } from "../team.data.js";
109
+ import { data as posts } from "../posts.data.js";
110
+ import FeatureCard from "./FeatureCard/Index.vue";
111
+
112
+ const { frontmatter } = useData();
113
+
114
+ const sortedTeamMembers = computed(() => {
115
+ return teamMembers;
116
+ });
117
+
118
+ const memberHasPosts = (memberId) => {
119
+ if (!memberId || memberId === "new_member") return false;
120
+ return posts.some((post) => {
121
+ return Array.isArray(post.authors) && post.authors.includes(memberId);
122
+ });
123
+ };
124
+ </script>
125
+
126
+ <style scoped>
127
+ .team-page {
128
+ width: 100%;
129
+ max-width: 1200px !important;
130
+ margin-left: auto !important;
131
+ margin-right: auto !important;
132
+ padding-left: 1.5rem !important;
133
+ padding-right: 1.5rem !important;
134
+ box-sizing: border-box !important;
135
+ padding-top: calc(var(--vp-nav-height) + 28px) !important;
136
+ padding-bottom: 48px !important;
137
+ word-break: break-word;
138
+ }
139
+
140
+ .team-page.has-hero {
141
+ padding-top: 2rem !important;
142
+ }
143
+
144
+ @media (min-width: 640px) {
145
+ .team-page {
146
+ padding-left: 1.5rem !important;
147
+ padding-right: 1.5rem !important;
148
+ padding-top: calc(var(--vp-nav-height) + 40px) !important;
149
+ padding-bottom: 64px !important;
150
+ }
151
+ .team-page.has-hero {
152
+ padding-top: 4.5rem !important;
153
+ }
154
+ }
155
+
156
+ @media (min-width: 960px) {
157
+ .team-page {
158
+ padding-left: 1.5rem !important;
159
+ padding-right: 1.5rem !important;
160
+ padding-top: calc(var(--vp-nav-height) + 58px) !important;
161
+ padding-bottom: 96px !important;
162
+ }
163
+ .team-page.has-hero {
164
+ padding-top: 8rem !important;
165
+ }
166
+ }
167
+
168
+ /* Page Header */
169
+ .team-header {
170
+ margin-bottom: 2.5rem;
171
+ border-bottom: 1px solid #f1f5f9; /* slate-100 */
172
+ padding-bottom: 1.5rem !important;
173
+ padding-top: 0 !important;
174
+ padding-left: 0 !important;
175
+ padding-right: 0 !important;
176
+ background: transparent !important;
177
+ min-height: auto !important;
178
+ height: auto !important;
179
+ }
180
+
181
+ .team-title {
182
+ font-family: "Inter", sans-serif;
183
+ font-style: normal;
184
+ font-weight: 700;
185
+ font-size: 28px;
186
+ line-height: 36px;
187
+ letter-spacing: 0.5px;
188
+ color: #020617; /* slate-950 */
189
+ margin-top: 0;
190
+ margin-bottom: 0.5rem;
191
+ }
192
+
193
+ @media (min-width: 768px) {
194
+ .team-title {
195
+ font-size: 32px;
196
+ line-height: 40px;
197
+ }
198
+ }
199
+
200
+ .team-subtitle {
201
+ font-family: "Inter", sans-serif;
202
+ font-style: normal;
203
+ font-weight: 400;
204
+ font-size: 16px;
205
+ line-height: 24px;
206
+ letter-spacing: 0.5px;
207
+ color: #64748b; /* slate-500 */
208
+ max-width: 620px;
209
+ margin: 0;
210
+ padding: 0;
211
+ }
212
+
213
+ /* Team Photo Section */
214
+ .team-photo-section {
215
+ margin-bottom: 3.5rem;
216
+ overflow: hidden;
217
+ border-radius: 1rem; /* 16px */
218
+ border: 1px solid rgba(226, 232, 240, 0.5); /* border-slate-100/50 */
219
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
220
+ }
221
+
222
+ .team-photo {
223
+ width: 100%;
224
+ height: auto;
225
+ object-fit: cover;
226
+ max-height: 500px;
227
+ }
228
+
229
+ /* Team Grid - custom grid definition to prevent conflict with Beercss global .grid */
230
+ .team-grid {
231
+ display: grid;
232
+ grid-template-columns: repeat(1, minmax(0, 1fr));
233
+ gap: 2rem;
234
+ }
235
+
236
+ @media (min-width: 640px) {
237
+ .team-grid {
238
+ grid-template-columns: repeat(2, minmax(0, 1fr));
239
+ }
240
+ }
241
+
242
+ @media (min-width: 1024px) {
243
+ .team-grid {
244
+ grid-template-columns: repeat(3, minmax(0, 1fr));
245
+ }
246
+ }
247
+
248
+ /* Deep overrides to integrate with theme-provided FeatureCard */
249
+ .team-member-card {
250
+ height: 100% !important;
251
+ }
252
+
253
+ .team-member-card :deep(.card-cover-link) {
254
+ aspect-ratio: 1 / 1 !important; /* Override cover layout aspect ratio to square portrait */
255
+ }
256
+
257
+ /* Left-align card titles and override margin */
258
+ .team-member-card :deep(.card-title-text) {
259
+ text-align: left !important;
260
+ margin-top: 0 !important;
261
+ margin-bottom: 0.5rem !important;
262
+ font-size: 1.25rem !important; /* exactly 20px matching card expectations */
263
+ font-weight: 700 !important;
264
+ }
265
+
266
+ /* Deep override card navigation to align properly */
267
+ .team-member-card :deep(.card-navigation) {
268
+ margin-top: 1rem !important;
269
+ padding-left: 1.5rem !important;
270
+ padding-bottom: 1.5rem !important;
271
+ }
272
+
273
+ .team-member-card :deep(.card-content-wrapper) {
274
+ display: flex !important;
275
+ flex-direction: column !important;
276
+ height: 100% !important;
277
+ }
278
+
279
+ .team-member-card :deep(.card-body-text) {
280
+ display: flex !important;
281
+ flex-direction: column !important;
282
+ flex-grow: 1 !important;
283
+ margin: 0 !important;
284
+ text-align: left !important;
285
+ }
286
+
287
+ /* Member Content Slot Elements (all left-aligned) */
288
+ .member-role {
289
+ display: block;
290
+ color: var(--primary) !important;
291
+ font-weight: 600 !important;
292
+ font-size: 0.875rem !important; /* 14px */
293
+ margin-top: 0 !important;
294
+ margin-bottom: 1rem !important;
295
+ text-align: left !important;
296
+ }
297
+
298
+ .member-divider {
299
+ display: block;
300
+ border: 0;
301
+ border-top: 1px solid #f1f5f9 !important; /* slate-100 */
302
+ margin-top: 0.5rem !important;
303
+ margin-bottom: 0.75rem !important;
304
+ }
305
+
306
+ .member-bio {
307
+ display: block;
308
+ font-size: 0.875rem !important; /* 14px */
309
+ line-height: 1.6 !important;
310
+ color: #475569 !important; /* slate-600 */
311
+ margin-top: 0 !important;
312
+ margin-bottom: 1.5rem !important;
313
+ text-align: left !important;
314
+ flex-grow: 1;
315
+ }
316
+
317
+ .member-footer {
318
+ display: flex;
319
+ flex-direction: column;
320
+ gap: 1rem;
321
+ margin-top: auto !important;
322
+ }
323
+
324
+ .social-links-row {
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: flex-start; /* Aligned to left */
328
+ gap: 0.5rem;
329
+ }
330
+
331
+ .social-link-btn {
332
+ width: 2.25rem;
333
+ height: 2.25rem;
334
+ display: flex;
335
+ align-items: center;
336
+ justify-content: center;
337
+ border-radius: 9999px;
338
+ color: #94a3b8; /* slate-400 */
339
+ transition: all 0.15s ease;
340
+ margin-left: -0.5rem; /* subtle offset alignment for first icon */
341
+ }
342
+
343
+ .social-link-btn:hover {
344
+ color: var(--primary);
345
+ background-color: var(--surface-container-high);
346
+ }
347
+ </style>