@eox/pages-theme-eox 0.4.17 → 0.5.0
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/package.json +1 -1
- package/src/Layout.vue +199 -97
- package/src/components/CTASection.vue +49 -0
- package/src/components/FeatureSection.vue +24 -10
- package/src/components/FeaturesGallery.vue +48 -27
- package/src/components/PricingTable.vue +442 -0
- package/src/index.js +5 -0
- package/src/style.css +93 -24
- package/src/vitepressConfig.mjs +2 -0
package/package.json
CHANGED
package/src/Layout.vue
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Layout :class="`layout-${frontmatter.layout}`">
|
|
3
|
-
<!-- <main class="responsive"> -->
|
|
4
3
|
<template #layout-top>
|
|
5
4
|
<slot name="layout-top"></slot>
|
|
6
5
|
<div class="top-nav at-top row center-align">
|
|
@@ -27,7 +26,7 @@
|
|
|
27
26
|
</a>
|
|
28
27
|
<div class="max"></div>
|
|
29
28
|
<button data-ui="#mobile-menu" class="circle transparent">
|
|
30
|
-
<i class="mdi mdi-close
|
|
29
|
+
<i class="mdi mdi-close"></i>
|
|
31
30
|
</button>
|
|
32
31
|
</nav>
|
|
33
32
|
<ul class="list border large-space">
|
|
@@ -37,14 +36,13 @@
|
|
|
37
36
|
:href="withBase(item.link)"
|
|
38
37
|
:target="item.target"
|
|
39
38
|
:rel="item.rel"
|
|
40
|
-
:class="
|
|
41
|
-
item.action
|
|
42
|
-
? 'button large medium-elevate cta'
|
|
43
|
-
: 'primary-text'
|
|
44
|
-
"
|
|
39
|
+
:class="item.action ? 'button large medium-elevate cta' : ''"
|
|
45
40
|
>
|
|
46
41
|
<span>{{ item.text }}</span>
|
|
47
|
-
<i
|
|
42
|
+
<i
|
|
43
|
+
v-if="item.action === 'primary'"
|
|
44
|
+
class="mdi mdi-arrow-right"
|
|
45
|
+
></i>
|
|
48
46
|
</a>
|
|
49
47
|
</li>
|
|
50
48
|
</ul>
|
|
@@ -58,12 +56,15 @@
|
|
|
58
56
|
:rel="item.rel"
|
|
59
57
|
:class="
|
|
60
58
|
item.action
|
|
61
|
-
? `button responsive large medium-elevate
|
|
59
|
+
? `button responsive large ${item.action === 'primary' ? 'primary medium-elevate' : 'border no-elevate'}`
|
|
62
60
|
: 'primary-text'
|
|
63
61
|
"
|
|
64
62
|
>
|
|
65
63
|
<span>{{ item.text }}</span>
|
|
66
|
-
<i
|
|
64
|
+
<i
|
|
65
|
+
v-if="item.action === 'primary'"
|
|
66
|
+
class="mdi mdi-arrow-right"
|
|
67
|
+
></i>
|
|
67
68
|
</a>
|
|
68
69
|
</nav>
|
|
69
70
|
</div>
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
<ul class="left-align no-margin">
|
|
86
87
|
<li v-for="item in theme.nav.filter((i) => !i.action)">
|
|
87
88
|
<a
|
|
89
|
+
class="button text"
|
|
88
90
|
:href="withBase(item.link)"
|
|
89
91
|
:target="item.target"
|
|
90
92
|
:rel="item.rel"
|
|
@@ -94,50 +96,83 @@
|
|
|
94
96
|
</ul>
|
|
95
97
|
</nav>
|
|
96
98
|
<div class="max"></div>
|
|
97
|
-
<nav>
|
|
99
|
+
<nav class="actions">
|
|
98
100
|
<ul class="left-align no-margin">
|
|
99
101
|
<li v-for="item in theme.nav.filter((item) => item.action)">
|
|
100
102
|
<a
|
|
101
103
|
class="button right-align"
|
|
102
|
-
:class="item.action === 'primary' ? '' : '
|
|
104
|
+
:class="item.action === 'primary' ? 'primary' : 'border'"
|
|
103
105
|
:href="withBase(item.link)"
|
|
104
106
|
:target="item.target"
|
|
105
107
|
:rel="item.rel"
|
|
106
108
|
>
|
|
107
109
|
<span>{{ item.text }}</span>
|
|
108
|
-
<i
|
|
110
|
+
<i
|
|
111
|
+
v-if="item.action === 'primary'"
|
|
112
|
+
class="mdi mdi-arrow-right"
|
|
113
|
+
></i>
|
|
109
114
|
</a>
|
|
110
115
|
</li>
|
|
111
116
|
</ul>
|
|
112
117
|
</nav>
|
|
113
118
|
</nav>
|
|
114
119
|
</div>
|
|
115
|
-
<header
|
|
120
|
+
<header
|
|
121
|
+
v-if="frontmatter.hero"
|
|
122
|
+
class="primary primary-gradient-background"
|
|
123
|
+
>
|
|
124
|
+
<div
|
|
125
|
+
:class="`large-padding hero-container ${frontmatter.hero.image ? 'image' : ''}`"
|
|
126
|
+
>
|
|
127
|
+
<iframe
|
|
128
|
+
v-if="frontmatter.hero.image?.src?.includes('youtube')"
|
|
129
|
+
class="hero-image large-elevate small-round"
|
|
130
|
+
style="grid-area: image; aspect-ratio: 16/9"
|
|
131
|
+
:src="frontmatter.hero.image.src"
|
|
132
|
+
frameborder="0"
|
|
133
|
+
allowfullscreen
|
|
134
|
+
/>
|
|
135
|
+
<img
|
|
136
|
+
v-else-if="frontmatter.hero.image"
|
|
137
|
+
class="hero-image large-elevate small-round"
|
|
138
|
+
style="grid-area: image"
|
|
139
|
+
:src="withBase(frontmatter.hero.image.src)"
|
|
140
|
+
/>
|
|
141
|
+
<div class="title" style="grid-area: title; align-content: end">
|
|
142
|
+
<h1 class="bold" style="font-size: clamp(2rem, 5vw, 45px)">
|
|
143
|
+
{{ frontmatter.hero.text }}
|
|
144
|
+
</h1>
|
|
145
|
+
<p>{{ frontmatter.hero.tagline }}</p>
|
|
146
|
+
</div>
|
|
147
|
+
<div style="grid-area: actions">
|
|
148
|
+
<a
|
|
149
|
+
v-for="(action, i) in frontmatter.hero.actions.filter(
|
|
150
|
+
(a) => a.theme === 'brand',
|
|
151
|
+
)"
|
|
152
|
+
:href="withBase(action.link)"
|
|
153
|
+
:target="action.target"
|
|
154
|
+
:rel="action.rel"
|
|
155
|
+
class="button extra small-margin"
|
|
156
|
+
:class="
|
|
157
|
+
action.theme === 'brand'
|
|
158
|
+
? 'primary-text surface medium-elevate'
|
|
159
|
+
: 'border white-border no-elevate white-text'
|
|
160
|
+
"
|
|
161
|
+
:style="i === 0 ? 'margin-left: 0 !important' : ''"
|
|
162
|
+
>
|
|
163
|
+
<span>{{ action.text }}</span>
|
|
164
|
+
<i
|
|
165
|
+
v-if="action.theme === 'brand'"
|
|
166
|
+
class="mdi mdi-arrow-right"
|
|
167
|
+
></i>
|
|
168
|
+
</a>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
116
171
|
<img
|
|
117
|
-
v-if="frontmatter.hero.
|
|
172
|
+
v-if="frontmatter.hero.background"
|
|
118
173
|
class="background-image"
|
|
119
|
-
:src="withBase(frontmatter.hero.
|
|
174
|
+
:src="withBase(frontmatter.hero.background.src)"
|
|
120
175
|
/>
|
|
121
|
-
<h1
|
|
122
|
-
class="bold medium-margin"
|
|
123
|
-
style="font-size: clamp(2rem, 5vw, 45px)"
|
|
124
|
-
>
|
|
125
|
-
{{ frontmatter.hero.text }}
|
|
126
|
-
</h1>
|
|
127
|
-
<p>{{ frontmatter.hero.tagline }}</p>
|
|
128
|
-
<div>
|
|
129
|
-
<a
|
|
130
|
-
v-for="action in frontmatter.hero.actions"
|
|
131
|
-
:href="withBase(action.link)"
|
|
132
|
-
:target="action.target"
|
|
133
|
-
:rel="action.rel"
|
|
134
|
-
class="button large medium-elevate cta"
|
|
135
|
-
:class="action.theme"
|
|
136
|
-
>
|
|
137
|
-
<span>{{ action.text }}</span>
|
|
138
|
-
<i class="mdi mdi-arrow-right"></i>
|
|
139
|
-
</a>
|
|
140
|
-
</div>
|
|
141
176
|
</header>
|
|
142
177
|
</template>
|
|
143
178
|
<template #page-bottom>
|
|
@@ -146,41 +181,68 @@
|
|
|
146
181
|
></FeaturesGallery>
|
|
147
182
|
</template>
|
|
148
183
|
<template #layout-bottom>
|
|
149
|
-
<footer class="surface row center-align">
|
|
150
|
-
<div class="holder">
|
|
151
|
-
<div class="">
|
|
152
|
-
|
|
184
|
+
<footer class="surface-container-low row center-align">
|
|
185
|
+
<div class="holder large-padding">
|
|
186
|
+
<div class="large-space"></div>
|
|
187
|
+
<div class="grid">
|
|
188
|
+
<div class="s12 l6">
|
|
153
189
|
<img
|
|
154
190
|
:src="withBase(theme.logo)"
|
|
155
191
|
:alt="`${site.title} logo`"
|
|
156
192
|
class="logo"
|
|
157
193
|
/>
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
194
|
+
<div class="small-space"></div>
|
|
195
|
+
<a
|
|
196
|
+
v-if="theme.nav.find((i) => i.link.includes('contact'))"
|
|
197
|
+
:href="theme.nav.find((i) => i.link.includes('contact')).link"
|
|
198
|
+
class="button small border no-margin"
|
|
199
|
+
style="color: var(--on-surface)"
|
|
200
|
+
>{{ theme.nav.find((i) => i.link.includes("contact")).text }}</a
|
|
201
|
+
>
|
|
202
|
+
<p v-html="theme.footer.copyright"></p>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="s12 l6">
|
|
205
|
+
<div class="grid large-line">
|
|
206
|
+
<div class="s6">
|
|
207
|
+
<p class="bold">About</p>
|
|
208
|
+
<p v-for="item in theme.nav.filter((i) => !i.action)">
|
|
209
|
+
<a
|
|
210
|
+
:href="withBase(item.link)"
|
|
211
|
+
:target="item.target"
|
|
212
|
+
:rel="item.rel"
|
|
213
|
+
class="link"
|
|
214
|
+
>{{ item.text }}</a
|
|
215
|
+
>
|
|
216
|
+
</p>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="s6">
|
|
219
|
+
<p class="bold">Legal</p>
|
|
220
|
+
<p>
|
|
221
|
+
<a
|
|
222
|
+
href="https://eox.at/impressum"
|
|
223
|
+
target="_blank"
|
|
224
|
+
class="link"
|
|
225
|
+
>About & Terms</a
|
|
226
|
+
>
|
|
227
|
+
</p>
|
|
228
|
+
<p>
|
|
229
|
+
<a
|
|
230
|
+
href="https://eox.at/privacy-notice"
|
|
231
|
+
target="_blank"
|
|
232
|
+
class="link"
|
|
233
|
+
>Privacy</a
|
|
234
|
+
>
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
169
239
|
</div>
|
|
170
|
-
<
|
|
171
|
-
<span v-html="theme.footer.copyright"></span>
|
|
172
|
-
<a href="https://eox.at/impressum" target="_blank">About & Terms</a>
|
|
173
|
-
<a href="https://eox.at/privacy-notice" target="_blank">Privacy</a>
|
|
174
|
-
</nav>
|
|
240
|
+
<div class="large-space"></div>
|
|
175
241
|
</div>
|
|
176
242
|
</footer>
|
|
177
243
|
<slot name="layout-bottom"></slot>
|
|
178
244
|
</template>
|
|
179
|
-
<!-- </main> -->
|
|
180
245
|
</Layout>
|
|
181
|
-
<!-- <main class="responsive">
|
|
182
|
-
|
|
183
|
-
</main> -->
|
|
184
246
|
</template>
|
|
185
247
|
|
|
186
248
|
<script setup>
|
|
@@ -193,7 +255,7 @@ if (!import.meta.env.SSR) {
|
|
|
193
255
|
const scrollListener = () => {
|
|
194
256
|
const nav = document.querySelector(".top-nav");
|
|
195
257
|
|
|
196
|
-
if (window.scrollY >
|
|
258
|
+
if (window.scrollY > nav.clientHeight) {
|
|
197
259
|
nav.classList.remove("at-top");
|
|
198
260
|
} else {
|
|
199
261
|
nav.classList.add("at-top");
|
|
@@ -206,10 +268,13 @@ if (!import.meta.env.SSR) {
|
|
|
206
268
|
<style>
|
|
207
269
|
.top-nav {
|
|
208
270
|
position: sticky;
|
|
209
|
-
z-index:
|
|
271
|
+
z-index: 2;
|
|
210
272
|
top: 0;
|
|
211
273
|
background: var(--surface);
|
|
212
|
-
box-shadow:
|
|
274
|
+
box-shadow:
|
|
275
|
+
0 1px 5px #15142e0d,
|
|
276
|
+
0 4px 16px #15142e1a,
|
|
277
|
+
inset 0 0 0 1px #ffffff80;
|
|
213
278
|
display: flex;
|
|
214
279
|
width: 100%;
|
|
215
280
|
transition: all 0.3s ease-in-out;
|
|
@@ -217,19 +282,37 @@ if (!import.meta.env.SSR) {
|
|
|
217
282
|
.top-nav.at-top {
|
|
218
283
|
box-shadow: none;
|
|
219
284
|
}
|
|
285
|
+
.top-nav.at-top .button.primary {
|
|
286
|
+
display: none;
|
|
287
|
+
}
|
|
288
|
+
.top-nav.at-top nav.actions {
|
|
289
|
+
gap: 0;
|
|
290
|
+
}
|
|
291
|
+
.top-nav:not(.at-top) > nav {
|
|
292
|
+
padding-top: 16px !important;
|
|
293
|
+
padding-bottom: 16px !important;
|
|
294
|
+
}
|
|
220
295
|
.Layout.layout-home .top-nav.at-top {
|
|
221
296
|
background: transparent;
|
|
222
297
|
color: #f7f8f8;
|
|
223
298
|
}
|
|
299
|
+
.Layout .top-nav nav .button {
|
|
300
|
+
color: var(--on-surface);
|
|
301
|
+
}
|
|
224
302
|
.Layout.layout-home .top-nav.at-top nav .button {
|
|
225
|
-
color:
|
|
303
|
+
color: var(--on-surface);
|
|
226
304
|
background: var(--surface);
|
|
227
305
|
}
|
|
228
306
|
.Layout.layout-home .top-nav.at-top nav .button.text {
|
|
229
307
|
color: #f7f8f8;
|
|
230
308
|
background: none;
|
|
231
309
|
}
|
|
232
|
-
.Layout.layout-home .top-nav.at-top nav
|
|
310
|
+
.Layout.layout-home .top-nav.at-top nav .button.border {
|
|
311
|
+
color: #f7f8f8;
|
|
312
|
+
border-color: #f7f8f855;
|
|
313
|
+
background: none;
|
|
314
|
+
}
|
|
315
|
+
.Layout.layout-home .top-nav.at-top > nav > a > .logo {
|
|
233
316
|
filter: brightness(0) invert(1);
|
|
234
317
|
}
|
|
235
318
|
img.logo {
|
|
@@ -251,14 +334,9 @@ nav.nav-desktop {
|
|
|
251
334
|
}
|
|
252
335
|
header {
|
|
253
336
|
margin-top: calc(var(--vp-nav-height) * -1);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
#0f9bff 0%,
|
|
257
|
-
#004170 60%,
|
|
258
|
-
#000c14 100%
|
|
259
|
-
);
|
|
337
|
+
padding-top: 10rem !important;
|
|
338
|
+
padding-bottom: 10rem !important;
|
|
260
339
|
height: 100svh;
|
|
261
|
-
padding: 0 6rem !important;
|
|
262
340
|
}
|
|
263
341
|
@media (max-width: 1024px) {
|
|
264
342
|
header {
|
|
@@ -276,41 +354,65 @@ header > img.background-image {
|
|
|
276
354
|
left: 0;
|
|
277
355
|
object-fit: cover;
|
|
278
356
|
opacity: 0.4;
|
|
357
|
+
z-index: 0;
|
|
358
|
+
}
|
|
359
|
+
header > .hero-container {
|
|
360
|
+
max-width: 1200px;
|
|
361
|
+
max-height: 80%;
|
|
362
|
+
margin-left: auto;
|
|
363
|
+
margin-right: auto;
|
|
364
|
+
display: grid;
|
|
365
|
+
grid-gap: 2rem;
|
|
366
|
+
grid-template-areas:
|
|
367
|
+
"title"
|
|
368
|
+
"actions"
|
|
369
|
+
"image";
|
|
370
|
+
grid-auto-rows: min-content;
|
|
371
|
+
text-align: center;
|
|
372
|
+
z-index: 1;
|
|
279
373
|
}
|
|
280
|
-
|
|
374
|
+
@media (min-width: 768px) {
|
|
375
|
+
header > .hero-container {
|
|
376
|
+
grid-template-areas:
|
|
377
|
+
"title"
|
|
378
|
+
"actions";
|
|
379
|
+
text-align: center;
|
|
380
|
+
}
|
|
381
|
+
header > .hero-container.image {
|
|
382
|
+
grid-template-areas:
|
|
383
|
+
"title image"
|
|
384
|
+
"actions image";
|
|
385
|
+
text-align: left;
|
|
386
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
header > .hero-container > .hero-image {
|
|
390
|
+
width: 100%;
|
|
391
|
+
object-fit: cover;
|
|
392
|
+
}
|
|
393
|
+
header > .hero-container > .title > h1 {
|
|
281
394
|
margin-bottom: 24px;
|
|
282
395
|
}
|
|
283
|
-
header > p {
|
|
396
|
+
header > .hero-container > .title > p {
|
|
284
397
|
font-size: 1.2rem;
|
|
285
|
-
margin-bottom: 40px;
|
|
286
|
-
}
|
|
287
|
-
header .cta.brand {
|
|
288
|
-
background-color: #f7f8f8;
|
|
289
|
-
color: #00060a;
|
|
290
398
|
}
|
|
291
|
-
header .cta
|
|
292
|
-
|
|
293
|
-
color: #f7f8f8;
|
|
399
|
+
header .cta:first-child {
|
|
400
|
+
margin-left: 0;
|
|
294
401
|
}
|
|
295
|
-
|
|
296
|
-
|
|
402
|
+
header .cta:last-child {
|
|
403
|
+
margin-right: 0;
|
|
297
404
|
}
|
|
298
405
|
.top-nav > nav,
|
|
299
406
|
.top-nav > .holder,
|
|
300
|
-
footer > .holder
|
|
407
|
+
footer > .holder,
|
|
408
|
+
.full-width > .holder {
|
|
301
409
|
width: 100%;
|
|
302
|
-
max-width:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
display: flex;
|
|
306
|
-
flex-direction: column;
|
|
410
|
+
max-width: 1200px;
|
|
411
|
+
margin-left: auto;
|
|
412
|
+
margin-right: auto;
|
|
307
413
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
@media (min-width: 768px) {
|
|
312
|
-
footer nav {
|
|
313
|
-
flex-direction: row;
|
|
314
|
-
}
|
|
414
|
+
.top-nav > nav,
|
|
415
|
+
.top-nav > .holder {
|
|
416
|
+
max-width: 1400px;
|
|
315
417
|
}
|
|
316
418
|
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="cta-section">
|
|
3
|
+
<div
|
|
4
|
+
class="center-align full-width"
|
|
5
|
+
:class="
|
|
6
|
+
dark !== undefined
|
|
7
|
+
? 'primary primary-gradient-background'
|
|
8
|
+
: 'surface-container-low'
|
|
9
|
+
"
|
|
10
|
+
>
|
|
11
|
+
<div class="large-space"></div>
|
|
12
|
+
<div class="large-space"></div>
|
|
13
|
+
<div class="holder large-padding">
|
|
14
|
+
<h3 class="large bold">
|
|
15
|
+
{{ title }}
|
|
16
|
+
</h3>
|
|
17
|
+
<p class="large-text">{{ tagline }}</p>
|
|
18
|
+
<a
|
|
19
|
+
class="button extra small-margin"
|
|
20
|
+
:class="dark !== undefined ? 'surface' : 'primary'"
|
|
21
|
+
:href="primaryLink"
|
|
22
|
+
>{{ primaryButton }} <i class="mdi mdi-arrow-right"></i
|
|
23
|
+
></a>
|
|
24
|
+
<a
|
|
25
|
+
v-if="secondaryButton"
|
|
26
|
+
class="button border extra small-margin"
|
|
27
|
+
:class="dark !== undefined ? 'white-text' : ''"
|
|
28
|
+
:href="secondaryLink"
|
|
29
|
+
>{{ secondaryButton }}</a
|
|
30
|
+
>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="large-space"></div>
|
|
33
|
+
<div class="large-space"></div>
|
|
34
|
+
<div class="large-space"></div>
|
|
35
|
+
</div>
|
|
36
|
+
</section>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
const props = defineProps([
|
|
41
|
+
"dark",
|
|
42
|
+
"primaryButton",
|
|
43
|
+
"primaryLink",
|
|
44
|
+
"secondaryButton",
|
|
45
|
+
"secondaryLink",
|
|
46
|
+
"tagline",
|
|
47
|
+
"title",
|
|
48
|
+
]);
|
|
49
|
+
</script>
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<section
|
|
3
|
-
:class="`feature-section full-width ${landing === undefined ? '' : 'landing'}`"
|
|
3
|
+
:class="`feature-section full-width ${landing === undefined ? '' : 'landing'} ${dark !== undefined ? 'dark' : ''} ${reverse !== undefined ? 'reverse' : ''}`"
|
|
4
4
|
>
|
|
5
|
-
<div
|
|
5
|
+
<div
|
|
6
|
+
class="holder"
|
|
7
|
+
:class="dark !== undefined ? 'primary-gradient-background' : ''"
|
|
8
|
+
>
|
|
6
9
|
<div class="text">
|
|
7
|
-
<nav class="
|
|
10
|
+
<nav class="">
|
|
8
11
|
<i :class="`mdi ${icon}`"></i>
|
|
9
12
|
{{ title }}
|
|
10
13
|
</nav>
|
|
11
|
-
<h5
|
|
14
|
+
<h5 v-if="landing !== undefined" class="title">{{ tagline }}</h5>
|
|
15
|
+
<h2 v-else class="title large">
|
|
16
|
+
<strong>{{ tagline }}</strong>
|
|
17
|
+
</h2>
|
|
12
18
|
<p><slot></slot></p>
|
|
13
|
-
<
|
|
19
|
+
<div class="small-space"></div>
|
|
20
|
+
<div>
|
|
14
21
|
<a
|
|
15
22
|
v-if="primaryLink !== undefined"
|
|
16
23
|
:href="withBase(primaryLink)"
|
|
17
24
|
:target="primaryLink.includes('https://') ? '_blank' : '_self'"
|
|
18
|
-
:class="`button primary medium-elevate`"
|
|
25
|
+
:class="`button primary medium-elevate no-margin`"
|
|
26
|
+
style="margin-right: 12px !important"
|
|
19
27
|
>
|
|
20
28
|
<span>{{ primaryButton || `Read more about ${title}` }}</span>
|
|
21
29
|
<i class="mdi mdi-arrow-right"></i>
|
|
@@ -24,14 +32,18 @@
|
|
|
24
32
|
v-if="secondaryLink !== undefined"
|
|
25
33
|
:href="withBase(secondaryLink)"
|
|
26
34
|
:target="secondaryLink.includes('https://') ? '_blank' : '_self'"
|
|
27
|
-
class="button border"
|
|
35
|
+
class="button border no-margin"
|
|
36
|
+
style="color: var(--on-surface); margin-top: 12px !important"
|
|
28
37
|
>
|
|
29
38
|
<span>{{ secondaryButton || "Contact sales" }}</span>
|
|
30
|
-
<i class="mdi mdi-arrow-right"></i>
|
|
31
39
|
</a>
|
|
32
|
-
</
|
|
40
|
+
</div>
|
|
33
41
|
</div>
|
|
34
|
-
<img
|
|
42
|
+
<img
|
|
43
|
+
:src="withBase(image)"
|
|
44
|
+
:alt="title"
|
|
45
|
+
class="small-round medium-elevate"
|
|
46
|
+
/>
|
|
35
47
|
</div>
|
|
36
48
|
</section>
|
|
37
49
|
</template>
|
|
@@ -39,11 +51,13 @@
|
|
|
39
51
|
<script setup>
|
|
40
52
|
import { withBase } from "vitepress";
|
|
41
53
|
const props = defineProps([
|
|
54
|
+
"dark",
|
|
42
55
|
"icon",
|
|
43
56
|
"image",
|
|
44
57
|
"landing",
|
|
45
58
|
"primaryButton",
|
|
46
59
|
"primaryLink",
|
|
60
|
+
"reverse",
|
|
47
61
|
"secondaryButton",
|
|
48
62
|
"secondaryLink",
|
|
49
63
|
"tagline",
|
|
@@ -5,6 +5,9 @@ import { data as features } from "../features.data.js";
|
|
|
5
5
|
const { page, site } = useData();
|
|
6
6
|
|
|
7
7
|
const featuresExcerpts = features.map((f) => {
|
|
8
|
+
if (import.meta.env.SSR) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
8
11
|
const el = document.createElement("html");
|
|
9
12
|
el.innerHTML = f.html;
|
|
10
13
|
const featureSection = el.querySelector("featuresection");
|
|
@@ -21,36 +24,54 @@ const featuresExcerpts = features.map((f) => {
|
|
|
21
24
|
</script>
|
|
22
25
|
|
|
23
26
|
<template>
|
|
24
|
-
<
|
|
25
|
-
<div class="
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
<div class="large-space"></div>
|
|
28
|
+
<div class="primary primary-gradient-background full-width">
|
|
29
|
+
<div class="large-space"></div>
|
|
30
|
+
<div class="holder large-padding">
|
|
31
|
+
<h5>More {{ site.title }} features:</h5>
|
|
32
|
+
<div class="medium-space"></div>
|
|
33
|
+
<div class="grid">
|
|
34
|
+
<ClientOnly>
|
|
35
|
+
<a
|
|
36
|
+
v-for="(feature, index) in featuresExcerpts.filter((f) => f)"
|
|
37
|
+
class="feature s12 m4 wave"
|
|
38
|
+
:href="withBase(feature.link)"
|
|
39
|
+
>
|
|
40
|
+
<article class="large-padding">
|
|
41
|
+
<div class="row top-align">
|
|
42
|
+
<img
|
|
43
|
+
v-if="feature.image"
|
|
44
|
+
class="round extra"
|
|
45
|
+
:src="withBase(feature.image)"
|
|
46
|
+
/>
|
|
47
|
+
<div class="max">
|
|
48
|
+
<h5 class="small">
|
|
49
|
+
<strong>{{ feature.title }}</strong>
|
|
50
|
+
</h5>
|
|
51
|
+
<p>{{ feature.description }}</p>
|
|
52
|
+
<div class="max"></div>
|
|
53
|
+
<nav>
|
|
54
|
+
<a class="link" :href="withBase(feature.link)">
|
|
55
|
+
<span>Read more</span>
|
|
56
|
+
</a>
|
|
57
|
+
</nav>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</article>
|
|
61
|
+
</a>
|
|
62
|
+
</ClientOnly>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="large-space"></div>
|
|
49
66
|
</div>
|
|
50
67
|
</template>
|
|
51
68
|
|
|
52
69
|
<style scoped>
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
.grid > a,
|
|
71
|
+
.grid > a > article {
|
|
72
|
+
height: 100%;
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
justify-content: space-between;
|
|
55
76
|
}
|
|
56
77
|
</style>
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- keep overflow visible so sticky header works -->
|
|
3
|
+
<div class="container">
|
|
4
|
+
<div class="grid">
|
|
5
|
+
<div class="m6 s12 left-align">
|
|
6
|
+
<h6 v-if="!localDetails.length" class="bold">{{ title }}</h6>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div :class="`m6 s12 ${isMobile ? 'left-align' : 'right-align'}`">
|
|
10
|
+
<label
|
|
11
|
+
v-for="(option, index) in localOptions"
|
|
12
|
+
:key="option.id"
|
|
13
|
+
class="checkbox"
|
|
14
|
+
>
|
|
15
|
+
<input
|
|
16
|
+
type="checkbox"
|
|
17
|
+
:id="option.id"
|
|
18
|
+
v-model="option.checked"
|
|
19
|
+
@change="updatePrices"
|
|
20
|
+
/>
|
|
21
|
+
<span class="margin" :for="option.id"
|
|
22
|
+
><h6 :class="`bold ${isMobile ? 'medium-text' : ''}`">
|
|
23
|
+
+ {{ option.label }}
|
|
24
|
+
</h6></span
|
|
25
|
+
>
|
|
26
|
+
</label>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div
|
|
30
|
+
class="grid m l surface-bright"
|
|
31
|
+
style="position: sticky; top: 88px; z-index: 1000; padding-top: 20px"
|
|
32
|
+
v-if="localDetails.length"
|
|
33
|
+
>
|
|
34
|
+
<div class="s12 m3"></div>
|
|
35
|
+
<div
|
|
36
|
+
v-for="(plan, index) in localPlans"
|
|
37
|
+
:key="`header-${index}`"
|
|
38
|
+
class="s12 m3 center-align"
|
|
39
|
+
>
|
|
40
|
+
<h6 class="primary-text bold">{{ plan.name }}</h6>
|
|
41
|
+
<ClientOnly>
|
|
42
|
+
<a
|
|
43
|
+
:href="addPlanConfig(contactLink, plan)"
|
|
44
|
+
class="button responsive bold margin-top-1 margin-bottom-2"
|
|
45
|
+
>
|
|
46
|
+
Contact us
|
|
47
|
+
</a>
|
|
48
|
+
</ClientOnly>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<h6 v-if="localDetails.length" class="bold padding-4">{{ title }}</h6>
|
|
53
|
+
<!-- Main Plans Table -->
|
|
54
|
+
<div class="wrapper" :style="gridStyle">
|
|
55
|
+
<div class="cell orig-col-1 m l top-margin">
|
|
56
|
+
<h6 v-if="!localDetails.length" class="bold small">Plans:</h6>
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
v-for="(plan, index) in localPlans"
|
|
60
|
+
:key="'head-' + index"
|
|
61
|
+
class="cell surface-container-low small-top-round bold top-margin"
|
|
62
|
+
:class="`orig-col-${index + 2}`"
|
|
63
|
+
>
|
|
64
|
+
<h6 class="primary-text bold">{{ plan.name }}</h6>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="cell orig-col-1 m l">Price (per month):</div>
|
|
68
|
+
<div
|
|
69
|
+
v-for="(plan, index) in localPlans"
|
|
70
|
+
:key="'price-' + index"
|
|
71
|
+
class="surface-container-low primary-text bold"
|
|
72
|
+
:class="`cell orig-col-${index + 2}`"
|
|
73
|
+
>
|
|
74
|
+
<div class="small-round surface-container medium-padding">
|
|
75
|
+
<h4 class="primary-text bold">€ {{ calculatePrice(plan) }},-</h4>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<template
|
|
80
|
+
v-for="(row, rowIndex) in getRowKeys(localPlans)"
|
|
81
|
+
:key="'row-' + rowIndex"
|
|
82
|
+
>
|
|
83
|
+
<div
|
|
84
|
+
v-if="typeof row === 'string'"
|
|
85
|
+
class="cell orig-col-1 m l"
|
|
86
|
+
v-html="row + ':'"
|
|
87
|
+
></div>
|
|
88
|
+
|
|
89
|
+
<div
|
|
90
|
+
v-for="(plan, planIndex) in localPlans"
|
|
91
|
+
:key="`${planIndex}-${row}`"
|
|
92
|
+
class="bold surface-container-low"
|
|
93
|
+
:class="`cell orig-col-${planIndex + 2} ${rowIndex === getRowKeys(localPlans).length - 1 && !config.showSales ? 'small-bottom-round' : ''}`"
|
|
94
|
+
>
|
|
95
|
+
<div
|
|
96
|
+
v-if="typeof plan.details[row] === 'string'"
|
|
97
|
+
class="s grey-text"
|
|
98
|
+
v-html="row"
|
|
99
|
+
></div>
|
|
100
|
+
<div
|
|
101
|
+
v-if="typeof plan.details[row] === 'object'"
|
|
102
|
+
:class="`${plan.details[row].alternative ? 'border primary-border small-round small-padding' : ''}`"
|
|
103
|
+
>
|
|
104
|
+
<span v-html="plan.details[row].text"></span>
|
|
105
|
+
<template v-if="plan.details[row].alternative">
|
|
106
|
+
<hr class="medium" />
|
|
107
|
+
<label class="alternative checkbox bold">
|
|
108
|
+
<input
|
|
109
|
+
type="checkbox"
|
|
110
|
+
:id="`${plan.name}-${row}`"
|
|
111
|
+
@input="$forceUpdate()"
|
|
112
|
+
/>
|
|
113
|
+
<span :for="`${plan.name}-${row}`"
|
|
114
|
+
><h6 class="small bold">
|
|
115
|
+
{{ plan.details[row].alternative.name }}
|
|
116
|
+
</h6></span
|
|
117
|
+
>
|
|
118
|
+
</label>
|
|
119
|
+
<span
|
|
120
|
+
:for="`${plan.name}-${row}`"
|
|
121
|
+
v-html="plan.details[row].alternative.text"
|
|
122
|
+
style="white-space: wrap; display: block"
|
|
123
|
+
></span>
|
|
124
|
+
</template>
|
|
125
|
+
</div>
|
|
126
|
+
<template v-else>
|
|
127
|
+
<span v-html="plan.details[row]"></span>
|
|
128
|
+
</template>
|
|
129
|
+
</div>
|
|
130
|
+
</template>
|
|
131
|
+
<div class="cell orig-col-1 m l"></div>
|
|
132
|
+
<div
|
|
133
|
+
v-for="(plan, index) in localPlans"
|
|
134
|
+
:key="'cta-' + index"
|
|
135
|
+
:class="`surface-container-low small-bottom-round cell orig-col-${index + 2} center-align`"
|
|
136
|
+
>
|
|
137
|
+
<a v-if="plan.link" :href="plan.link"
|
|
138
|
+
>See all features
|
|
139
|
+
<svg
|
|
140
|
+
style="width: 16px; height: 16px"
|
|
141
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
142
|
+
viewBox="0 0 24 24"
|
|
143
|
+
>
|
|
144
|
+
<title>chevron-right</title>
|
|
145
|
+
<path
|
|
146
|
+
d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"
|
|
147
|
+
/>
|
|
148
|
+
</svg>
|
|
149
|
+
</a>
|
|
150
|
+
</div>
|
|
151
|
+
<!-- Contact us section -->
|
|
152
|
+
<div class="cell orig-col-1 m l"></div>
|
|
153
|
+
<div
|
|
154
|
+
v-if="showSales"
|
|
155
|
+
v-for="(plan, index) in localPlans"
|
|
156
|
+
:key="'cta-' + index"
|
|
157
|
+
:class="`surface-container-low small-bottom-round cell orig-col-${index + 2} center-align`"
|
|
158
|
+
>
|
|
159
|
+
<ClientOnly>
|
|
160
|
+
<a
|
|
161
|
+
v-if="showSales"
|
|
162
|
+
:href="addPlanConfig(contactLink, plan)"
|
|
163
|
+
class="button responsive bold"
|
|
164
|
+
>Contact us</a
|
|
165
|
+
>
|
|
166
|
+
</ClientOnly>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Add-on Details Tables -->
|
|
171
|
+
<template v-for="(detail, detailIndex) in localDetails" :key="detailIndex">
|
|
172
|
+
<hr class="margin-top-4" />
|
|
173
|
+
<div class="margin-top-2">
|
|
174
|
+
<div class="s12">
|
|
175
|
+
<h5 class="bold">{{ detail.title }}</h5>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div class="wrapper" :style="secondaryGridStyle">
|
|
180
|
+
<div class="cell cell orig-col-1 m l">
|
|
181
|
+
<div class="">Additional price:</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div
|
|
184
|
+
v-for="(plan, detailPlanIndex) in detail.plans"
|
|
185
|
+
:key="detailPlanIndex"
|
|
186
|
+
class="surface-container-low primary-text bold small-top-round top-margin"
|
|
187
|
+
:class="`cell orig-col-${detailPlanIndex + 2}`"
|
|
188
|
+
>
|
|
189
|
+
<h6 class="primary-text bold s">{{ plan.name }}</h6>
|
|
190
|
+
<h6 v-if="plan.additionalPrice" class="primary-text bold">
|
|
191
|
+
+ € {{ plan.additionalPrice }},-
|
|
192
|
+
</h6>
|
|
193
|
+
</div>
|
|
194
|
+
<template
|
|
195
|
+
v-for="(row, rowIndex) in getRowKeys(detail.plans)"
|
|
196
|
+
:key="'row-' + rowIndex"
|
|
197
|
+
>
|
|
198
|
+
<div
|
|
199
|
+
v-if="typeof row === 'string'"
|
|
200
|
+
class="cell orig-col-1 m l"
|
|
201
|
+
v-html="row + ':'"
|
|
202
|
+
></div>
|
|
203
|
+
<div
|
|
204
|
+
v-for="(plan, planIndex) in detail.plans"
|
|
205
|
+
:key="`${planIndex}-${row}`"
|
|
206
|
+
class="bold surface-container-low"
|
|
207
|
+
:class="`cell orig-col-${planIndex + 2} ${rowIndex === getRowKeys(detail.plans).length - 1 && !config.showSales ? 'small-bottom-round' : ''}`"
|
|
208
|
+
>
|
|
209
|
+
<div class="s grey-text" v-html="row + ':'"></div>
|
|
210
|
+
<template
|
|
211
|
+
v-if="
|
|
212
|
+
typeof plan.details[row] === 'object' &&
|
|
213
|
+
plan.details[row]?.checkmark
|
|
214
|
+
"
|
|
215
|
+
>
|
|
216
|
+
<i
|
|
217
|
+
class="mdi mdi-check-circle-outline"
|
|
218
|
+
style="color: #4caf50"
|
|
219
|
+
></i>
|
|
220
|
+
</template>
|
|
221
|
+
<template v-else>
|
|
222
|
+
<span v-html="plan.details[row]"></span>
|
|
223
|
+
</template>
|
|
224
|
+
</div>
|
|
225
|
+
</template>
|
|
226
|
+
</div>
|
|
227
|
+
</template>
|
|
228
|
+
|
|
229
|
+
<p class="small grey-text m-4" v-if="showVAT">
|
|
230
|
+
* All prices are given excluding VAT. Prices are valid until 2025-06-30
|
|
231
|
+
</p>
|
|
232
|
+
</div>
|
|
233
|
+
</template>
|
|
234
|
+
|
|
235
|
+
<script>
|
|
236
|
+
export default {
|
|
237
|
+
name: "PricingTable",
|
|
238
|
+
props: {
|
|
239
|
+
config: {
|
|
240
|
+
type: Object,
|
|
241
|
+
default: () => ({
|
|
242
|
+
title: "Pricing Options",
|
|
243
|
+
showSales: true,
|
|
244
|
+
showVAT: true,
|
|
245
|
+
options: [],
|
|
246
|
+
plans: [],
|
|
247
|
+
details: [],
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
data() {
|
|
252
|
+
return {
|
|
253
|
+
title: "",
|
|
254
|
+
showSales: true,
|
|
255
|
+
contactLink: "",
|
|
256
|
+
showVAT: true,
|
|
257
|
+
localOptions: [],
|
|
258
|
+
localPlans: [],
|
|
259
|
+
localDetails: [],
|
|
260
|
+
isMobile: !import.meta.env.SSR && window.innerWidth <= 768,
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
computed: {
|
|
264
|
+
gridStyle() {
|
|
265
|
+
// dynamically asign number of cols in grid when not on mobile
|
|
266
|
+
if (!this.isMobile) {
|
|
267
|
+
return {
|
|
268
|
+
"grid-template-columns": `repeat(${this.localPlans.length + 1}, 1fr)`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
secondaryGridStyle() {
|
|
273
|
+
if (!this.isMobile && this.localDetails) {
|
|
274
|
+
return {
|
|
275
|
+
"grid-template-columns": `repeat(${this.localDetails[0].plans.length + 1}, 1fr)`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
methods: {
|
|
281
|
+
calculatePrice(plan) {
|
|
282
|
+
const basePrice = plan.basePrice;
|
|
283
|
+
const additionalPrice = this.localOptions
|
|
284
|
+
.filter((option) => option.checked)
|
|
285
|
+
.reduce((sum, option) => sum + (option.modifier || 0), 0);
|
|
286
|
+
|
|
287
|
+
return basePrice + additionalPrice;
|
|
288
|
+
},
|
|
289
|
+
getRowKeys(plansArray) {
|
|
290
|
+
const keys = new Set();
|
|
291
|
+
plansArray.forEach((plan) => {
|
|
292
|
+
Object.keys(plan.details).forEach((key) => keys.add(key));
|
|
293
|
+
});
|
|
294
|
+
return Array.from(keys);
|
|
295
|
+
},
|
|
296
|
+
updatePrices() {
|
|
297
|
+
this.$forceUpdate();
|
|
298
|
+
},
|
|
299
|
+
initializeData() {
|
|
300
|
+
this.title = this.config.title ?? this.title;
|
|
301
|
+
this.showSales = this.config.showSales ?? this.showSales;
|
|
302
|
+
this.contactLink = this.config.contactLink ?? this.contactLink;
|
|
303
|
+
this.showVAT = this.config.showVAT ?? this.showVAT;
|
|
304
|
+
this.localOptions = this.config.options ?? this.localOptions;
|
|
305
|
+
this.localPlans = this.config.plans ?? this.localPlans;
|
|
306
|
+
this.localDetails = this.config.details ?? this.localDetails;
|
|
307
|
+
},
|
|
308
|
+
handleResize() {
|
|
309
|
+
this.isMobile = window.innerWidth <= 768;
|
|
310
|
+
},
|
|
311
|
+
updateOrderStyles() {
|
|
312
|
+
if (import.meta.env.SSR) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const count = this.localPlans.length + 1;
|
|
316
|
+
for (let i = 1; i <= count; i++) {
|
|
317
|
+
const elements = document.querySelectorAll(`.orig-col-${i}`);
|
|
318
|
+
elements.forEach((el) => {
|
|
319
|
+
el.style.order = this.isMobile ? i : "";
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
addPlanConfig(contactLink, plan) {
|
|
324
|
+
if (import.meta.env.SSR) {
|
|
325
|
+
return "";
|
|
326
|
+
}
|
|
327
|
+
const parts = contactLink.split("?");
|
|
328
|
+
let search = parts[1] || "";
|
|
329
|
+
const params = new URLSearchParams(search);
|
|
330
|
+
params.append("plan", plan.name);
|
|
331
|
+
const alternative = Object.entries(plan.details).find(
|
|
332
|
+
([key, value]) => value.alternative,
|
|
333
|
+
);
|
|
334
|
+
if (alternative) {
|
|
335
|
+
const checked = document.querySelector(
|
|
336
|
+
`[id='${plan.name}-${alternative[0]}']`,
|
|
337
|
+
)?.checked;
|
|
338
|
+
if (checked) {
|
|
339
|
+
params.append("alternative", alternative[1].alternative.name);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (this.localOptions.length > 0)
|
|
343
|
+
[
|
|
344
|
+
this.localOptions.forEach((o) => {
|
|
345
|
+
const checked = document.querySelector(`input#${o.id}`)?.checked;
|
|
346
|
+
if (checked) {
|
|
347
|
+
params.append("option", o.label);
|
|
348
|
+
}
|
|
349
|
+
}),
|
|
350
|
+
];
|
|
351
|
+
search = params.toString();
|
|
352
|
+
return `${parts[0]}${search ? `?${search}` : ""}`;
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
created() {
|
|
356
|
+
this.initializeData();
|
|
357
|
+
},
|
|
358
|
+
mounted() {
|
|
359
|
+
window.addEventListener("resize", this.handleResize);
|
|
360
|
+
this.updateOrderStyles();
|
|
361
|
+
},
|
|
362
|
+
beforeDestroy() {
|
|
363
|
+
window.removeEventListener("resize", this.handleResize);
|
|
364
|
+
},
|
|
365
|
+
watch: {
|
|
366
|
+
options: {
|
|
367
|
+
handler() {
|
|
368
|
+
this.initializeData();
|
|
369
|
+
},
|
|
370
|
+
deep: true,
|
|
371
|
+
},
|
|
372
|
+
plans: {
|
|
373
|
+
handler() {
|
|
374
|
+
this.initializeData();
|
|
375
|
+
},
|
|
376
|
+
deep: true,
|
|
377
|
+
},
|
|
378
|
+
details: {
|
|
379
|
+
handler() {
|
|
380
|
+
this.initializeData();
|
|
381
|
+
},
|
|
382
|
+
deep: true,
|
|
383
|
+
},
|
|
384
|
+
localOptions: {
|
|
385
|
+
handler() {
|
|
386
|
+
this.$forceUpdate();
|
|
387
|
+
},
|
|
388
|
+
deep: true,
|
|
389
|
+
},
|
|
390
|
+
isMobile() {
|
|
391
|
+
if (!import.meta.env.SSR) {
|
|
392
|
+
this.updateOrderStyles();
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
</script>
|
|
398
|
+
|
|
399
|
+
<style scoped>
|
|
400
|
+
.container {
|
|
401
|
+
padding: 3rem 0;
|
|
402
|
+
z-index: 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.wrapper {
|
|
406
|
+
display: grid;
|
|
407
|
+
grid-template-columns: 1fr;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.cell {
|
|
411
|
+
padding: 8px 16px 24px 16px;
|
|
412
|
+
overflow: hidden;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.small-top-round {
|
|
416
|
+
border-top-left-radius: 16px;
|
|
417
|
+
border-top-right-radius: 16px;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.small-bottom-round {
|
|
421
|
+
border-bottom-left-radius: 16px;
|
|
422
|
+
border-bottom-right-radius: 16px;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@media screen and (min-width: 769px) {
|
|
426
|
+
.wrapper {
|
|
427
|
+
column-gap: 20px;
|
|
428
|
+
row-gap: 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.cell {
|
|
432
|
+
order: initial;
|
|
433
|
+
margin-top: 0;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.alternative:has(input:not(:checked)) + span,
|
|
438
|
+
span:has(+ * + * > input:checked) {
|
|
439
|
+
text-decoration: line-through;
|
|
440
|
+
opacity: 0.5;
|
|
441
|
+
}
|
|
442
|
+
</style>
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import DefaultTheme from "vitepress/theme";
|
|
2
2
|
import FeatureSection from "./components/FeatureSection.vue";
|
|
3
3
|
import FeaturesGallery from "./components/FeaturesGallery.vue";
|
|
4
|
+
import PricingTable from "./components/PricingTable.vue";
|
|
5
|
+
import CTASection from "./components/CTASection.vue";
|
|
4
6
|
import Layout from "./Layout.vue";
|
|
5
7
|
import "./style.css";
|
|
6
8
|
|
|
@@ -10,6 +12,8 @@ export default {
|
|
|
10
12
|
enhanceApp({ app, router, siteData }) {
|
|
11
13
|
app.component("FeatureSection", FeatureSection);
|
|
12
14
|
app.component("FeaturesGallery", FeaturesGallery);
|
|
15
|
+
app.component("PricingTable", PricingTable);
|
|
16
|
+
app.component("CTASection", CTASection);
|
|
13
17
|
|
|
14
18
|
router.onAfterRouteChanged = () => {
|
|
15
19
|
if (!import.meta.env.SSR) {
|
|
@@ -42,6 +46,7 @@ export default {
|
|
|
42
46
|
:root, body.light {
|
|
43
47
|
/* EOxUI */
|
|
44
48
|
--primary: ${siteData.value.themeConfig.theme.primaryColor};
|
|
49
|
+
--secondary: ${siteData.value.themeConfig.theme.secondaryColor};
|
|
45
50
|
}
|
|
46
51
|
`),
|
|
47
52
|
);
|
package/src/style.css
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
body {
|
|
8
8
|
margin: 0;
|
|
9
|
-
font-size: 1rem;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
.VPNav,
|
|
@@ -24,11 +23,18 @@ body {
|
|
|
24
23
|
.VPContent h2.large,
|
|
25
24
|
.VPContent h3.large {
|
|
26
25
|
font-size: normal;
|
|
27
|
-
font-weight: normal;
|
|
28
26
|
line-height: normal;
|
|
29
27
|
border-top: none;
|
|
30
28
|
}
|
|
31
29
|
|
|
30
|
+
.VPContent h1,
|
|
31
|
+
.VPContent h2,
|
|
32
|
+
.VPContent h3,
|
|
33
|
+
.VPContent h4,
|
|
34
|
+
.VPContent h5 {
|
|
35
|
+
font-weight: bold;
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
*:has(> nav.top:not(.s, .m, .l)) {
|
|
33
39
|
padding-block-start: 0;
|
|
34
40
|
}
|
|
@@ -52,8 +58,58 @@ body {
|
|
|
52
58
|
.VPPage {
|
|
53
59
|
margin: auto;
|
|
54
60
|
width: 100%;
|
|
55
|
-
max-width:
|
|
56
|
-
padding: 48px 24px;
|
|
61
|
+
max-width: 1200px;
|
|
62
|
+
padding: 48px 24px 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.VPHome {
|
|
66
|
+
margin-bottom: 0 !important;
|
|
67
|
+
padding-left: 0;
|
|
68
|
+
padding-right: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.VPHome .vp-doc h1 {
|
|
72
|
+
font-size: 3.5625rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.VPHome .vp-doc h2 {
|
|
76
|
+
font-size: 2.8125rem;
|
|
77
|
+
border-top: none;
|
|
78
|
+
margin-block-start: 1rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.VPHome .vp-doc h3 {
|
|
82
|
+
font-size: 2.25rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.VPHome .vp-doc h4 {
|
|
86
|
+
font-size: 2rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.VPHome .vp-doc h5 {
|
|
90
|
+
font-size: 1.75rem;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.VPHome .vp-doc h1 a.header-anchor,
|
|
94
|
+
.VPHome .vp-doc h2 a.header-anchor,
|
|
95
|
+
.VPHome .vp-doc h3 a.header-anchor,
|
|
96
|
+
.VPHome .vp-doc h4 a.header-anchor,
|
|
97
|
+
.VPHome .vp-doc h5 a.header-anchor {
|
|
98
|
+
display: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.VPLocalNav {
|
|
102
|
+
display: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.vp-doc.container {
|
|
106
|
+
padding-left: 24px;
|
|
107
|
+
padding-right: 24px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**/
|
|
111
|
+
footer {
|
|
112
|
+
margin-top: 0 !important;
|
|
57
113
|
}
|
|
58
114
|
|
|
59
115
|
/**/
|
|
@@ -66,6 +122,10 @@ button.text:hover,
|
|
|
66
122
|
box-shadow: none;
|
|
67
123
|
}
|
|
68
124
|
|
|
125
|
+
:is(button, .button).border {
|
|
126
|
+
border-color: var(--outline-variant);
|
|
127
|
+
}
|
|
128
|
+
|
|
69
129
|
/* Feature sections */
|
|
70
130
|
.full-width {
|
|
71
131
|
width: 100svw;
|
|
@@ -86,17 +146,20 @@ button.text:hover,
|
|
|
86
146
|
flex-direction: column;
|
|
87
147
|
align-items: center;
|
|
88
148
|
justify-content: space-between;
|
|
89
|
-
|
|
90
|
-
|
|
149
|
+
margin-left: auto;
|
|
150
|
+
margin-right: auto;
|
|
151
|
+
max-width: 1200px;
|
|
152
|
+
padding-bottom: 32px;
|
|
91
153
|
}
|
|
92
154
|
.feature-section.landing .holder {
|
|
155
|
+
padding: 32px 0;
|
|
93
156
|
background: var(--surface-container-low);
|
|
94
157
|
}
|
|
95
158
|
.feature-section.dark .holder {
|
|
96
159
|
background: radial-gradient(
|
|
97
160
|
farthest-corner at 100% 100%,
|
|
98
161
|
#0f9bff 0%,
|
|
99
|
-
|
|
162
|
+
var(--primary) 60%,
|
|
100
163
|
#000c14 100%
|
|
101
164
|
);
|
|
102
165
|
color: #fff;
|
|
@@ -111,6 +174,11 @@ button.text:hover,
|
|
|
111
174
|
.feature-section .text {
|
|
112
175
|
padding: 48px;
|
|
113
176
|
}
|
|
177
|
+
.feature-section:not(.landing) .text {
|
|
178
|
+
padding-top: 0;
|
|
179
|
+
padding-left: 24px;
|
|
180
|
+
padding-right: 24px;
|
|
181
|
+
}
|
|
114
182
|
.feature-section .icon {
|
|
115
183
|
background: #61616133;
|
|
116
184
|
padding: 16px;
|
|
@@ -125,35 +193,27 @@ button.text:hover,
|
|
|
125
193
|
}
|
|
126
194
|
.feature-section img {
|
|
127
195
|
max-width: 80%;
|
|
128
|
-
border-radius: 8px;
|
|
129
196
|
background: var(--surface-bright);
|
|
130
|
-
box-shadow: 0px 11px 15px 0px #00000033;
|
|
131
197
|
}
|
|
132
198
|
@media (min-width: 1024px) {
|
|
133
|
-
.feature-section
|
|
199
|
+
.feature-section.landing,
|
|
200
|
+
.cta-section {
|
|
134
201
|
padding: 64px;
|
|
135
202
|
padding: clamp(64px, 10%, 128px);
|
|
136
203
|
}
|
|
137
|
-
.feature-section:not(.feature-section.landing) {
|
|
138
|
-
padding-top: 0;
|
|
139
|
-
}
|
|
140
204
|
.feature-section .holder {
|
|
141
205
|
flex-direction: row;
|
|
142
206
|
padding: 68px 0;
|
|
143
207
|
border-radius: 12px;
|
|
144
208
|
}
|
|
209
|
+
.feature-section:not(.feature-section.landing) .holder {
|
|
210
|
+
padding-top: 0;
|
|
211
|
+
padding-bottom: 120px;
|
|
212
|
+
}
|
|
145
213
|
.feature-section img {
|
|
146
214
|
max-width: 60%;
|
|
147
215
|
transform: translateX(10%);
|
|
148
216
|
}
|
|
149
|
-
.feature-section.reverse.dark .holder {
|
|
150
|
-
background: radial-gradient(
|
|
151
|
-
farthest-corner at 0% 100%,
|
|
152
|
-
#0f9bff 0%,
|
|
153
|
-
#004170 60%,
|
|
154
|
-
#000c14 100%
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
217
|
.feature-section.reverse .text {
|
|
158
218
|
order: 1;
|
|
159
219
|
}
|
|
@@ -166,10 +226,10 @@ button.text:hover,
|
|
|
166
226
|
}
|
|
167
227
|
}
|
|
168
228
|
@media (min-width: 1280px) {
|
|
169
|
-
.feature-section {
|
|
170
|
-
padding: 128px;
|
|
229
|
+
.feature-section.landing {
|
|
230
|
+
padding: 128px 24px;
|
|
171
231
|
}
|
|
172
|
-
.feature-section .holder {
|
|
232
|
+
.feature-section.landing .holder {
|
|
173
233
|
flex-direction: row;
|
|
174
234
|
padding: 68px 0;
|
|
175
235
|
}
|
|
@@ -223,3 +283,12 @@ button.text:hover,
|
|
|
223
283
|
}
|
|
224
284
|
}
|
|
225
285
|
}
|
|
286
|
+
|
|
287
|
+
.primary-gradient-background {
|
|
288
|
+
background: radial-gradient(
|
|
289
|
+
74.48% 130.95% at 50% -12.27%,
|
|
290
|
+
hsl(from var(--primary) h s calc(l + 7)) 0%,
|
|
291
|
+
var(--primary) 45%,
|
|
292
|
+
hsl(from var(--primary) h s calc(l - 20)) 100%
|
|
293
|
+
) !important;
|
|
294
|
+
}
|
package/src/vitepressConfig.mjs
CHANGED
|
@@ -14,6 +14,8 @@ export const generate = (brandConfig) => ({
|
|
|
14
14
|
siteTitle: false,
|
|
15
15
|
theme: {
|
|
16
16
|
primaryColor: brandConfig.theme?.primary_color,
|
|
17
|
+
secondaryColor:
|
|
18
|
+
brandConfig.theme?.secondary_color || brandConfig.theme?.primary_color,
|
|
17
19
|
},
|
|
18
20
|
logo: brandConfig.logo,
|
|
19
21
|
footer: {
|