@eox/pages-theme-eox 2.0.0-alpha.6 → 2.0.0-alpha.8

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 CHANGED
@@ -145,26 +145,40 @@ The theme provides several built-in components and layout features. Some are dri
145
145
 
146
146
  ### Hero Section
147
147
 
148
- To add a Hero section to the top of a page, use the `hero` frontmatter object.
148
+ To add a Hero section to the top of a page, use the `hero` frontmatter object. Heros feature a dark gradient transitioning from **DARK (top) to LIGHT (bottom)** (`.hero-gradient-bg`), allowing the top navigation bar to seamlessly sit transparently over the hero. On the landing page, the hero is larger and incorporates an isometric line pattern.
149
149
 
150
150
  ```yaml
151
151
  ---
152
152
  hero:
153
- title: "Welcome to My Site"
154
- tagline: "Building the future of things"
153
+ text: "Welcome to My Site" # or title: "Welcome to My Site"
154
+ tagline: "Building the future of geospatial engineering"
155
155
  image:
156
156
  src: "/hero-image.png"
157
157
  alt: "Hero Image"
158
158
  actions:
159
159
  - text: "Get Started"
160
160
  link: "/start"
161
- type: "primary"
161
+ theme: "brand" # "brand" (primary surface), "secondary", or "alt" (bordered)
162
162
  - text: "Learn More"
163
163
  link: "/about"
164
- type: "secondary"
164
+ theme: "secondary"
165
165
  ---
166
166
  ```
167
167
 
168
+ #### Hero on Aggregator Pages (`blog`, `team`, `tags`)
169
+
170
+ Pages using aggregator layouts (`layout: team`, `layout: blog`, or `layout: tags`) support optional heroes:
171
+
172
+ - **Without Hero (when `hero` is omitted or `hero: false`)**: The page renders a standard in-page header title beneath the solid top navigation bar.
173
+ - **With Hero (`hero: true` or custom `hero: { text, tagline, ... }`)**: The page renders the full dark hero with the `#000C14` → `#004170` gradient spanning seamlessly behind the transparent navigation bar. Setting `hero: true` automatically supplies appropriate layout titles (including dynamic `Tag: <tag>` and `Author: <author>` for filtered blog listings). Custom text, taglines, images, and actions can also be provided via the `hero` frontmatter object.
174
+
175
+ #### Gradient Design Standards
176
+
177
+ The theme standardizes two distinct gradient directions for dark visual elements using `#000C14` and `#004170`:
178
+
179
+ - **Hero Gradient (`.hero-gradient-bg`)**: Always transitions from **DARK (top) to LIGHT (bottom)**, running from `#000C14` (top) to `#004170` (bottom). Used for the landing page hero and all sub-page heroes. This anchors the fixed header area with dark contrast under navigation links while flowing naturally into lighter content below.
180
+ - **Dark Sections Gradient (`.primary-gradient-bg` / `.dark-section-gradient-bg`)**: Used in intermediate dark page sections further down the page (such as `<CTASection dark>`, feature highlights, or dark showcase galleries). Always transitions from **LIGHT (top) to DARK (bottom)**, running from `#004170` (top) to `#000C14` (bottom).
181
+
168
182
  ### Feature Section
169
183
 
170
184
  Use the `<FeatureSection>` component to highlight specific features.
@@ -223,7 +237,7 @@ A flexible pricing table component.
223
237
 
224
238
  #### CTA Section
225
239
 
226
- A Call-to-Action section typically placed at the bottom of a page to encourage user engagement.
240
+ A Call-to-Action section typically placed at the bottom of a page to encourage user engagement. By default, it renders with a clean white (`surface-container-lowest`) background, or a dark gradient when the `dark` prop is passed.
227
241
 
228
242
  ```html
229
243
  <CTASection
@@ -334,7 +348,7 @@ A standard content card with an icon or image, title, optional chips, metadata,
334
348
  section-title="All Features"
335
349
  variant="default"
336
350
  columns="3/2/1"
337
- background="surface-container-low"
351
+ background="surface-container-lowest"
338
352
  :cards="[
339
353
  {
340
354
  id: 1,
@@ -48,9 +48,31 @@ window.__helpersMock = {
48
48
  }
49
49
  },
50
50
  isActive: (item, path) => {
51
- if (item.link === path) return true;
52
- if (item.items) {
53
- return item.items.some((sub) => window.__helpersMock.isActive(sub, path));
51
+ if (!item || !path) return false;
52
+ if (item.activeMatch) {
53
+ try {
54
+ return new RegExp(item.activeMatch).test(path);
55
+ } catch (e) {
56
+ return false;
57
+ }
58
+ }
59
+ if (
60
+ item.items &&
61
+ item.items.some((sub) => window.__helpersMock.isActive(sub, path))
62
+ ) {
63
+ return true;
64
+ }
65
+ if (item.link) {
66
+ const normalize = (p) =>
67
+ decodeURI(p)
68
+ .replace(/[?#].*$/, "")
69
+ .replace(/\.html$/, "")
70
+ .replace(/\/$/, "");
71
+ const linkPath = normalize(item.link);
72
+ const currentPath = normalize(path);
73
+ if (linkPath === currentPath) return true;
74
+ if (linkPath === "") return currentPath === "";
75
+ return currentPath === linkPath || currentPath.startsWith(linkPath + "/");
54
76
  }
55
77
  return false;
56
78
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eox/pages-theme-eox",
3
- "version": "2.0.0-alpha.6",
3
+ "version": "2.0.0-alpha.8",
4
4
  "type": "module",
5
5
  "description": "Vitepress Theme with EOX branding",
6
6
  "main": "src/index.js",
package/src/Layout.vue CHANGED
@@ -1,10 +1,11 @@
1
1
  <template>
2
2
  <div
3
3
  v-if="frontmatter.layout === 'blog'"
4
- class="layout-blog min-h-screen flex flex-col"
4
+ class="Layout layout-blog min-h-screen flex flex-col"
5
5
  >
6
6
  <NavBar />
7
7
  <NewsBanner v-if="theme.newsBanner" :content="theme.newsBanner" />
8
+ <HeroSection />
8
9
  <main class="flex-grow">
9
10
  <BlogList />
10
11
  </main>
@@ -13,10 +14,11 @@
13
14
  </div>
14
15
  <div
15
16
  v-else-if="frontmatter.layout === 'tags'"
16
- class="layout-tags min-h-screen flex flex-col"
17
+ class="Layout layout-tags min-h-screen flex flex-col"
17
18
  >
18
19
  <NavBar />
19
20
  <NewsBanner v-if="theme.newsBanner" :content="theme.newsBanner" />
21
+ <HeroSection />
20
22
  <main class="flex-grow">
21
23
  <TagList />
22
24
  </main>
@@ -25,10 +27,11 @@
25
27
  </div>
26
28
  <div
27
29
  v-else-if="frontmatter.layout === 'team'"
28
- class="layout-team min-h-screen flex flex-col"
30
+ class="Layout layout-team min-h-screen flex flex-col"
29
31
  >
30
32
  <NavBar />
31
33
  <NewsBanner v-if="theme.newsBanner" :content="theme.newsBanner" />
34
+ <HeroSection />
32
35
  <main class="flex-grow">
33
36
  <TeamPage />
34
37
  </main>
@@ -1,7 +1,10 @@
1
1
  <template>
2
- <div class="blog-landing max-w-[1200px] mx-auto px-6 md:px-8">
3
- <!-- Page Header -->
4
- <div class="blog-landing-header" role="banner">
2
+ <div
3
+ class="blog-landing max-w-[1200px] mx-auto px-6 md:px-8"
4
+ :class="{ 'has-hero': Boolean(frontmatter.hero) }"
5
+ >
6
+ <!-- Page Header (shown only when hero is not present) -->
7
+ <div v-if="!frontmatter.hero" class="blog-landing-header" role="banner">
5
8
  <div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
6
9
  <div>
7
10
  <h1 class="blog-landing-title">EOX Blog</h1>
@@ -10,28 +13,28 @@
10
13
  </p>
11
14
  </div>
12
15
  </div>
16
+ </div>
13
17
 
14
- <!-- Active Filter Banner -->
15
- <div v-if="activeTag || activeAuthor" class="active-filter-banner">
16
- <span class="filter-label">Active filter:</span>
17
- <span
18
- v-if="activeTag"
19
- :style="getTagStyle(activeTag)"
20
- class="active-filter-tag shadow-sm inline-flex items-center gap-1.5"
21
- >
22
- {{ activeTag }}
23
- </span>
24
- <span
25
- v-if="activeAuthor"
26
- class="active-filter-author shadow-sm inline-flex items-center gap-1.5"
27
- >
28
- Author: {{ getAuthorName(activeAuthor) }}
29
- </span>
30
- <button @click="clearFilters" class="clear-filter-btn">
31
- <i class="mdi mdi-close-circle"></i>
32
- <span>Clear Filter</span>
33
- </button>
34
- </div>
18
+ <!-- Active Filter Banner -->
19
+ <div v-if="activeTag || activeAuthor" class="active-filter-banner mb-6">
20
+ <span class="filter-label">Active filter:</span>
21
+ <span
22
+ v-if="activeTag"
23
+ :style="getTagStyle(activeTag)"
24
+ class="active-filter-tag shadow-sm inline-flex items-center gap-1.5"
25
+ >
26
+ {{ activeTag }}
27
+ </span>
28
+ <span
29
+ v-if="activeAuthor"
30
+ class="active-filter-author shadow-sm inline-flex items-center gap-1.5"
31
+ >
32
+ Author: {{ getAuthorName(activeAuthor) }}
33
+ </span>
34
+ <button @click="clearFilters" class="clear-filter-btn">
35
+ <i class="mdi mdi-close-circle"></i>
36
+ <span>Clear Filter</span>
37
+ </button>
35
38
  </div>
36
39
 
37
40
  <!-- Grid of Blog Posts -->
@@ -195,7 +198,7 @@ const calculatedHotTags = computed(() => {
195
198
  });
196
199
 
197
200
  const router = useRouter();
198
- const { params } = useData();
201
+ const { params, frontmatter } = useData();
199
202
 
200
203
  const activeTag = ref("");
201
204
  const activeAuthor = ref("");
@@ -345,6 +348,10 @@ function clearFilters() {
345
348
  padding-bottom: 48px !important;
346
349
  }
347
350
 
351
+ .blog-landing.has-hero {
352
+ padding-top: 2rem !important;
353
+ }
354
+
348
355
  @media (min-width: 640px) {
349
356
  .blog-landing {
350
357
  padding-left: 1.5rem !important;
@@ -352,6 +359,9 @@ function clearFilters() {
352
359
  padding-top: calc(var(--vp-nav-height) + 40px) !important;
353
360
  padding-bottom: 64px !important;
354
361
  }
362
+ .blog-landing.has-hero {
363
+ padding-top: 2.5rem !important;
364
+ }
355
365
  }
356
366
 
357
367
  @media (min-width: 960px) {
@@ -361,6 +371,9 @@ function clearFilters() {
361
371
  padding-top: calc(var(--vp-nav-height) + 58px) !important;
362
372
  padding-bottom: 96px !important;
363
373
  }
374
+ .blog-landing.has-hero {
375
+ padding-top: 3rem !important;
376
+ }
364
377
  }
365
378
 
366
379
  .blog-landing-header {
@@ -4,7 +4,7 @@
4
4
  :class="
5
5
  dark !== undefined
6
6
  ? 'primary primary-gradient-bg'
7
- : 'surface-container-low'
7
+ : 'surface-container-lowest'
8
8
  "
9
9
  >
10
10
  <div class="large-space"></div>
@@ -150,7 +150,12 @@ function formatDate(dateString) {
150
150
  ]"
151
151
  >
152
152
  <!-- Cover Image for cover/blog/rich cards -->
153
- <a v-if="isCoverLayout" :href="resolvedLinkHref" class="card-cover-link">
153
+ <a
154
+ v-if="isCoverLayout && resolvedLinkHref"
155
+ :href="resolvedLinkHref"
156
+ :target="link?.target ?? '_blank'"
157
+ class="card-cover-link"
158
+ >
154
159
  <img
155
160
  :src="resolvedImage"
156
161
  alt=""
@@ -158,6 +163,14 @@ function formatDate(dateString) {
158
163
  loading="lazy"
159
164
  />
160
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>
161
174
 
162
175
  <div class="card-content-wrapper">
163
176
  <i
@@ -532,6 +545,8 @@ function formatDate(dateString) {
532
545
  }
533
546
 
534
547
  /* card title overrides to make sure it respects the 20px request */
548
+ h5.small.card-title-text,
549
+ .card-title-text,
535
550
  .VPHome .vp-doc h5.small.card-title-text {
536
551
  font-size: 20px !important;
537
552
  line-height: 1.4 !important;
@@ -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 {
@@ -50,6 +50,7 @@ const { cards, sectionTitle, background, variant, columns } = defineProps({
50
50
 
51
51
  const cardComponents = {
52
52
  default: FeatureCardDefault,
53
+ cover: FeatureCardDefault,
53
54
  poster: defineAsyncComponent(() => import("./FeatureCard/Poster.vue")),
54
55
  };
55
56
 
@@ -74,6 +75,8 @@ function resolveUrl(url) {
74
75
  function normalizeFeature(feature) {
75
76
  return {
76
77
  ...feature,
78
+ content: feature.content || feature.description,
79
+ chips: feature.chips || feature.tags,
77
80
  image: resolveUrl(feature.image),
78
81
  link: feature.link
79
82
  ? {
@@ -172,6 +175,7 @@ const gridCols = computed(() => {
172
175
  :metadata="feature.metadata"
173
176
  :link="feature.link"
174
177
  :secondaryLink="feature.secondaryLink"
178
+ :variant="variant"
175
179
  :class="`${cardBackground}`"
176
180
  />
177
181
  </div>
@@ -186,6 +190,7 @@ const gridCols = computed(() => {
186
190
  :metadata="feature.metadata"
187
191
  :link="feature.link"
188
192
  :secondaryLink="feature.secondaryLink"
193
+ :variant="variant"
189
194
  :class="`${cardBackground}`"
190
195
  />
191
196
  </div>
@@ -42,7 +42,7 @@ describe("<Footer />", () => {
42
42
 
43
43
  it("renders legal links", () => {
44
44
  cy.mount(Footer);
45
- cy.contains("a", "About").should("have.attr", "href", "/about");
45
+ cy.contains("a", "Imprint").should("have.attr", "href", "/about");
46
46
  cy.contains("a", "Terms & Conditions").should(
47
47
  "have.attr",
48
48
  "href",
@@ -51,6 +51,43 @@ describe("<Footer />", () => {
51
51
  cy.contains("a", "Privacy").should("have.attr", "href", "/privacy");
52
52
  });
53
53
 
54
+ it("renders uncategorized nav links under site title", () => {
55
+ cy.mount(Footer);
56
+ cy.contains("p.bold", "EOX Site").should("exist");
57
+ cy.contains("a", "Link 1").should("have.attr", "href", "/link1");
58
+ });
59
+
60
+ it("renders uncategorized nav links under brandConfig title when present", () => {
61
+ __setMockData({
62
+ theme: {
63
+ theme: {
64
+ brandConfig: {
65
+ title: "Custom Brand",
66
+ },
67
+ },
68
+ },
69
+ });
70
+ cy.mount(Footer);
71
+ cy.contains("p.bold", "Custom Brand").should("exist");
72
+ cy.contains("a", "Link 1").should("have.attr", "href", "/link1");
73
+ });
74
+
75
+ it("renders dropdown items under category title", () => {
76
+ __setMockData({
77
+ theme: {
78
+ nav: [
79
+ {
80
+ text: "Our Company",
81
+ items: [{ text: "About", link: "/about" }],
82
+ },
83
+ ],
84
+ },
85
+ });
86
+ cy.mount(Footer);
87
+ cy.contains("p.bold", "Our Company").should("exist");
88
+ cy.contains("a", "About").should("have.attr", "href", "/about");
89
+ });
90
+
54
91
  it("renders footer message with HTML content", () => {
55
92
  __setMockData({
56
93
  site: { title: "EOX Site" },
@@ -87,6 +124,7 @@ describe("<Footer />", () => {
87
124
  },
88
125
  i18n: {
89
126
  About: "Über uns",
127
+ Imprint: "Impressum",
90
128
  Legal: "Rechtliches",
91
129
  "Powered by": "Unterstützt von",
92
130
  },
@@ -96,7 +134,7 @@ describe("<Footer />", () => {
96
134
  },
97
135
  });
98
136
  cy.mount(Footer);
99
- cy.contains("Über uns").should("exist");
137
+ cy.contains("Impressum").should("exist");
100
138
  cy.contains("Rechtliches").should("exist");
101
139
  });
102
140
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <footer class="surface-container-low row center-align">
2
+ <footer class="surface-container-lowest row center-align">
3
3
  <div class="holder large-padding">
4
4
  <div class="large-space"></div>
5
5
  <div class="grid">
@@ -36,7 +36,9 @@
36
36
  class="footer-column-item"
37
37
  v-if="theme.nav.filter((i) => !i.action && i.link).length"
38
38
  >
39
- <p class="bold">{{ t("About", theme.i18n) }}</p>
39
+ <p class="bold">
40
+ {{ theme.theme?.brandConfig?.title || site.title }}
41
+ </p>
40
42
  <p v-for="item in theme.nav.filter((i) => !i.action && i.link)">
41
43
  <a
42
44
  :href="withBase(item.link)"
@@ -72,7 +74,7 @@
72
74
  "
73
75
  target="_blank"
74
76
  class="link"
75
- >{{ t("About", theme.i18n) }}</a
77
+ >{{ t("Imprint", theme.i18n) }}</a
76
78
  >
77
79
  </p>
78
80
  <p v-for="(term, index) in termsLinks" :key="term.id || index">
@@ -1,40 +1,45 @@
1
1
  <template>
2
- <header v-if="frontmatter.hero" class="primary primary-gradient-bg">
2
+ <header v-if="heroData" class="primary hero-gradient-bg">
3
3
  <div
4
- :class="`large-padding hero-container ${frontmatter.hero.image ? 'image' : ''}`"
4
+ :class="`large-padding hero-container ${heroData.image ? 'image' : ''}`"
5
5
  >
6
6
  <iframe
7
- v-if="frontmatter.hero.image?.src?.includes('youtube')"
7
+ v-if="heroData.image?.src?.includes('youtube')"
8
8
  class="hero-image large-elevate small-round"
9
9
  style="grid-area: image; aspect-ratio: 16/9"
10
- :src="frontmatter.hero.image.src"
10
+ :src="heroData.image.src"
11
11
  frameborder="0"
12
12
  allowfullscreen
13
13
  />
14
14
  <video
15
- v-else-if="frontmatter.hero.video"
15
+ v-else-if="heroData.video"
16
16
  class="hero-image large-elevate small-round"
17
- :src="withBase(frontmatter.hero.video.src)"
17
+ :src="withBase(heroData.video.src)"
18
18
  style="grid-area: image; aspect-ratio: 16/9"
19
19
  autoplay
20
20
  muted
21
21
  loop
22
22
  />
23
23
  <img
24
- v-else-if="frontmatter.hero.image"
24
+ v-else-if="heroData.image"
25
25
  class="hero-image large-elevate small-round"
26
26
  style="grid-area: image"
27
- :src="withBase(frontmatter.hero.image.src)"
27
+ :src="withBase(heroData.image.src)"
28
+ :alt="heroData.image.alt || heroData.text"
28
29
  />
29
30
  <div class="title" style="grid-area: title">
30
31
  <h1 class="bold" style="font-size: clamp(2.25rem, 5vw, 3.3rem)">
31
- {{ frontmatter.hero.text }}
32
+ {{ heroData.text }}
32
33
  </h1>
33
- <p v-html="frontmatter.hero.tagline"></p>
34
+ <p v-if="heroData.tagline" v-html="heroData.tagline"></p>
34
35
  </div>
35
- <div style="grid-area: actions">
36
+ <div
37
+ v-if="heroData.actions && heroData.actions.length > 0"
38
+ style="grid-area: actions"
39
+ >
36
40
  <a
37
- v-for="(action, i) in frontmatter.hero.actions"
41
+ v-for="(action, i) in heroData.actions"
42
+ :key="i"
38
43
  :href="withBase(action.link)"
39
44
  :target="action.target"
40
45
  :rel="action.rel"
@@ -62,17 +67,100 @@
62
67
  </div>
63
68
  </div>
64
69
  <img
65
- v-if="frontmatter.hero.background"
70
+ v-if="heroData.background"
66
71
  class="background-image"
67
- :src="withBase(frontmatter.hero.background.src)"
72
+ :src="withBase(heroData.background.src)"
73
+ :alt="heroData.background.alt || ''"
68
74
  />
69
75
  </header>
70
76
  </template>
71
77
 
72
78
  <script setup>
79
+ import { computed } from "vue";
73
80
  import { useData, withBase } from "vitepress";
74
81
  import { trackEvent } from "../helpers";
75
- const { frontmatter } = useData();
82
+
83
+ const props = defineProps({
84
+ text: String,
85
+ title: String,
86
+ tagline: String,
87
+ actions: Array,
88
+ image: Object,
89
+ video: Object,
90
+ background: Object,
91
+ });
92
+
93
+ const { frontmatter, params } = useData();
94
+
95
+ const heroData = computed(() => {
96
+ if (props.text || props.title || props.tagline) {
97
+ return {
98
+ text: props.text || props.title,
99
+ tagline: props.tagline,
100
+ actions: props.actions,
101
+ image: props.image,
102
+ video: props.video,
103
+ background: props.background,
104
+ };
105
+ }
106
+
107
+ if (!frontmatter.value.hero || frontmatter.value.hero === false) {
108
+ return null;
109
+ }
110
+
111
+ // If hero is an object with custom properties
112
+ if (typeof frontmatter.value.hero === "object") {
113
+ return {
114
+ ...frontmatter.value.hero,
115
+ text:
116
+ frontmatter.value.hero.text ||
117
+ frontmatter.value.hero.title ||
118
+ frontmatter.value.title,
119
+ };
120
+ }
121
+
122
+ // If hero is true (boolean), provide intelligent layout defaults
123
+ if (frontmatter.value.hero === true) {
124
+ if (frontmatter.value.layout === "team") {
125
+ return {
126
+ text: frontmatter.value.title || "Our Team",
127
+ tagline:
128
+ frontmatter.value.tagline ||
129
+ "Meet the diverse team of geospatial experts, engineers, and visionaries driving EOX forward.",
130
+ };
131
+ }
132
+ if (frontmatter.value.layout === "blog") {
133
+ const tag = params?.value?.tag;
134
+ const author = params?.value?.author;
135
+ let blogTitle = frontmatter.value.title || "EOX Blog";
136
+ if (tag) {
137
+ blogTitle = `Tag: ${tag}`;
138
+ } else if (author) {
139
+ blogTitle = `Author: ${author}`;
140
+ }
141
+ return {
142
+ text: blogTitle,
143
+ tagline:
144
+ frontmatter.value.tagline ||
145
+ "Insights, updates, and deep dives from our geospatial experts.",
146
+ };
147
+ }
148
+ if (frontmatter.value.layout === "tags") {
149
+ return {
150
+ text: frontmatter.value.title || "Blog Tags",
151
+ tagline:
152
+ frontmatter.value.tagline ||
153
+ "Explore our blog posts by topics and technology pillars.",
154
+ };
155
+ }
156
+ return {
157
+ text: frontmatter.value.title || "",
158
+ tagline: frontmatter.value.tagline || frontmatter.value.description || "",
159
+ };
160
+ }
161
+
162
+ return null;
163
+ });
76
164
  </script>
77
165
 
78
166
  <style scoped>
@@ -65,6 +65,7 @@ onMounted(() => {
65
65
 
66
66
  <style>
67
67
  section.logo-section {
68
+ background-color: var(--surface-container-lowest, #ffffff);
68
69
  margin-block-start: 3rem;
69
70
  }
70
71
  .logo-section .logo-row {
@@ -1,8 +1,11 @@
1
1
  <template>
2
2
  <template v-for="item in items">
3
- <li v-if="item.items && item.text">
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)"