@voidzero-dev/vitepress-theme 2.0.0 → 2.1.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 +7 -6
- package/src/aliases.js +14 -0
- package/src/vitepress/assets/clients/clickup.svg +5 -0
- package/src/vitepress/assets/clients/stripe.svg +3 -0
- package/src/vitepress/components/oss/Footer.vue +4 -21
- package/src/vitepress/components/oss/Header.vue +82 -180
- package/src/vitepress/components/oss/Sponsors.vue +3 -3
- package/src/vitepress/components/oss/TopBanner.vue +20 -79
- package/src/vitepress/components/oss/TrustedBy.vue +1 -1
- package/src/vitepress/components/vite/Community.vue +3 -3
- package/src/vitepress/components/vite/FeatureGrid1.vue +63 -0
- package/src/vitepress/components/vite/{FeatureGrid.vue → FeatureGrid2.vue} +8 -10
- package/src/vitepress/components/vite/Hero.vue +6 -15
- package/src/vitepress/components/vitepress-default/VPDocOutlineItem.vue +2 -2
- package/src/vitepress/components/vitepress-default/VPFlyout.vue +1 -1
- package/src/vitepress/components/vitepress-default/VPMenuLink.vue +1 -1
- package/src/vitepress/components/vitepress-default/VPNavBarMenuLink.vue +1 -1
- package/src/vitepress/components/vitepress-default/VPSidebarItem.vue +1 -1
- package/src/vitepress/components/vitepress-default/VPSocialLink.vue +1 -2
- package/src/vitepress/fonts/APK-Protocol-Semi-Bold.woff2 +0 -0
- package/src/vitepress/fonts/inter-italic-cyrillic-ext.woff2 +0 -0
- package/src/vitepress/fonts/inter-italic-cyrillic.woff2 +0 -0
- package/src/vitepress/fonts/inter-italic-greek-ext.woff2 +0 -0
- package/src/vitepress/fonts/inter-italic-greek.woff2 +0 -0
- package/src/vitepress/fonts/inter-italic-latin-ext.woff2 +0 -0
- package/src/vitepress/fonts/inter-italic-latin.woff2 +0 -0
- package/src/vitepress/fonts/inter-italic-vietnamese.woff2 +0 -0
- package/src/vitepress/fonts/inter-roman-cyrillic-ext.woff2 +0 -0
- package/src/vitepress/fonts/inter-roman-cyrillic.woff2 +0 -0
- package/src/vitepress/fonts/inter-roman-greek-ext.woff2 +0 -0
- package/src/vitepress/fonts/inter-roman-greek.woff2 +0 -0
- package/src/vitepress/fonts/inter-roman-latin-ext.woff2 +0 -0
- package/src/vitepress/fonts/inter-roman-latin.woff2 +0 -0
- package/src/vitepress/fonts/inter-roman-vietnamese.woff2 +0 -0
- package/src/vitepress/index.ts +64 -230
- package/src/vitepress/layouts/VPLayout.vue +2 -17
- package/src/vitepress/styles/tokens.css +194 -10
- package/src/vitepress/types/theme-context.ts +33 -0
- package/src/vitepress/assets/clients/beehiiv.svg +0 -30
- package/src/vitepress/assets/clients/excalidraw.svg +0 -82
- package/src/vitepress/assets/clients/get-your-guide.svg +0 -1
- package/src/vitepress/assets/clients/posthog.svg +0 -1
- package/src/vitepress/assets/clients/ramp.svg +0 -1
- package/src/vitepress/assets/clients/shopee.svg +0 -55
- package/src/vitepress/components/vite/FeaturePanel1.vue +0 -41
- package/src/vitepress/components/vite/FeaturePanel2.vue +0 -37
- package/src/vitepress/components/vite/FeaturePanel3.vue +0 -43
- package/src/vitepress/components/vite/FeaturePanel4.vue +0 -46
- package/src/vitepress/components/voidzero/Footer.vue +0 -65
- package/src/vitepress/components/voidzero/Header.vue +0 -560
- package/src/vitepress/components/voidzero/Megamenu.vue +0 -190
- package/src/vitepress/components/voidzero/about/CareerCTA.vue +0 -56
- package/src/vitepress/components/voidzero/about/Hero.vue +0 -206
- package/src/vitepress/components/voidzero/about/Investors.vue +0 -112
- package/src/vitepress/components/voidzero/about/TeamGrid.vue +0 -161
- package/src/vitepress/components/voidzero/about/TeamSectionHeading.vue +0 -13
- package/src/vitepress/components/voidzero/blog/BlogArchive.vue +0 -223
- package/src/vitepress/components/voidzero/blog/BlogSingleContent.vue +0 -364
- package/src/vitepress/components/voidzero/blog/BlogSingleHero.vue +0 -113
- package/src/vitepress/components/voidzero/blog/BlogSingleRelated.vue +0 -92
- package/src/vitepress/components/voidzero/blog/FeaturedArticles.vue +0 -146
- package/src/vitepress/components/voidzero/blog/types.ts +0 -56
- package/src/vitepress/components/voidzero/home/CaseStudySlider.vue +0 -235
- package/src/vitepress/components/voidzero/home/CustomersSectionHeading.vue +0 -5
- package/src/vitepress/components/voidzero/home/GitHubStats.vue +0 -27
- package/src/vitepress/components/voidzero/home/Hero.vue +0 -69
- package/src/vitepress/components/voidzero/home/Investors.vue +0 -30
- package/src/vitepress/components/voidzero/home/NewsletterCTA.vue +0 -23
- package/src/vitepress/components/voidzero/home/OpenSourceSectionHeading.vue +0 -6
- package/src/vitepress/components/voidzero/home/OpenSourceSectionProjects.vue +0 -419
- package/src/vitepress/components/voidzero/home/Resources.vue +0 -144
- package/src/vitepress/components/voidzero/home/Statistics.vue +0 -507
- package/src/vitepress/components/voidzero/home/StatisticsSectionHeading.vue +0 -5
- package/src/vitepress/components/voidzero/home/TeamCTA.vue +0 -17
- package/src/vitepress/components/voidzero/home/TrustedBy.vue +0 -248
- package/src/vitepress/components/voidzero/home/VitePlusSectionFeatures.vue +0 -55
- package/src/vitepress/components/voidzero/home/VitePlusSectionHeading.vue +0 -17
- package/src/vitepress/fonts/KHTeka-Medium.woff2 +0 -0
- package/src/vitepress/fonts/KHTeka-Regular.woff2 +0 -0
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {useData, useRoute} from 'vitepress'
|
|
3
|
-
import {ref, onMounted, onUnmounted, watch, nextTick, computed} from 'vue'
|
|
4
|
-
import 'vue3-carousel/carousel.css'
|
|
5
|
-
import Megamenu from './Megamenu.vue'
|
|
6
|
-
|
|
7
|
-
interface FeaturedArticle {
|
|
8
|
-
title: string
|
|
9
|
-
url?: string
|
|
10
|
-
link?: string
|
|
11
|
-
cover?: string
|
|
12
|
-
image?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface GithubStat {
|
|
16
|
-
repo: string
|
|
17
|
-
stars: number
|
|
18
|
-
contributors: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Props {
|
|
22
|
-
featuredArticles?: FeaturedArticle[]
|
|
23
|
-
githubStats?: GithubStat[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
27
|
-
featuredArticles: () => [],
|
|
28
|
-
githubStats: () => []
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
const {theme} = useData()
|
|
32
|
-
const nav = theme.value.nav
|
|
33
|
-
|
|
34
|
-
const route = useRoute()
|
|
35
|
-
|
|
36
|
-
// Calculate total GitHub stars from all repos
|
|
37
|
-
const totalGithubStars = computed(() => {
|
|
38
|
-
if (!props.githubStats || props.githubStats.length === 0) {
|
|
39
|
-
return 0
|
|
40
|
-
}
|
|
41
|
-
return props.githubStats.reduce((sum, stat) => sum + stat.stars, 0)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// Format stars for display (e.g., 103.6k)
|
|
45
|
-
const formattedStars = computed(() => {
|
|
46
|
-
const stars = totalGithubStars.value
|
|
47
|
-
if (stars >= 1000000) {
|
|
48
|
-
return (stars / 1000000).toFixed(1) + 'M'
|
|
49
|
-
} else if (stars >= 1000) {
|
|
50
|
-
return (stars / 1000).toFixed(1) + 'k'
|
|
51
|
-
}
|
|
52
|
-
return stars.toString()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
// Megamenu state
|
|
56
|
-
const megamenuOpen = ref(false)
|
|
57
|
-
const activeMegamenuItem = ref<string | null>(null)
|
|
58
|
-
const megamenuRef = ref<HTMLElement | null>(null)
|
|
59
|
-
const hoverTimeout = ref<number | null>(null)
|
|
60
|
-
|
|
61
|
-
// Mobile menu state
|
|
62
|
-
const mobileMenuOpen = ref(false)
|
|
63
|
-
const expandedMobileItem = ref<string | null>(null)
|
|
64
|
-
|
|
65
|
-
// Close megamenu when clicking outside
|
|
66
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
67
|
-
const target = e.target as HTMLElement
|
|
68
|
-
if (megamenuRef.value && !megamenuRef.value.contains(target)) {
|
|
69
|
-
closeMegamenu()
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Handle keyboard navigation
|
|
74
|
-
const handleKeydown = (e: KeyboardEvent) => {
|
|
75
|
-
if (e.key === 'Escape') {
|
|
76
|
-
if (mobileMenuOpen.value) {
|
|
77
|
-
closeMobileMenu()
|
|
78
|
-
} else if (megamenuOpen.value) {
|
|
79
|
-
closeMegamenu()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Open megamenu for a specific nav item
|
|
85
|
-
const openMegamenu = (navItemLink: string) => {
|
|
86
|
-
if (hoverTimeout.value) {
|
|
87
|
-
clearTimeout(hoverTimeout.value)
|
|
88
|
-
hoverTimeout.value = null
|
|
89
|
-
}
|
|
90
|
-
activeMegamenuItem.value = navItemLink
|
|
91
|
-
megamenuOpen.value = true
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Close megamenu with optional delay
|
|
95
|
-
const closeMegamenu = (delay: number = 0) => {
|
|
96
|
-
if (delay > 0) {
|
|
97
|
-
hoverTimeout.value = window.setTimeout(() => {
|
|
98
|
-
megamenuOpen.value = false
|
|
99
|
-
activeMegamenuItem.value = null
|
|
100
|
-
}, delay)
|
|
101
|
-
} else {
|
|
102
|
-
if (hoverTimeout.value) {
|
|
103
|
-
clearTimeout(hoverTimeout.value)
|
|
104
|
-
hoverTimeout.value = null
|
|
105
|
-
}
|
|
106
|
-
megamenuOpen.value = false
|
|
107
|
-
activeMegamenuItem.value = null
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Handle nav item hover (desktop)
|
|
112
|
-
const handleNavItemMouseEnter = (navItem: any) => {
|
|
113
|
-
if (navItem.megamenu) {
|
|
114
|
-
openMegamenu(navItem.link)
|
|
115
|
-
} else {
|
|
116
|
-
closeMegamenu(200)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Handle nav item hover leave
|
|
121
|
-
const handleNavItemMouseLeave = () => {
|
|
122
|
-
closeMegamenu(200)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Handle megamenu hover (keep it open)
|
|
126
|
-
const handleMegamenuMouseEnter = () => {
|
|
127
|
-
if (hoverTimeout.value) {
|
|
128
|
-
clearTimeout(hoverTimeout.value)
|
|
129
|
-
hoverTimeout.value = null
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Handle megamenu leave
|
|
134
|
-
const handleMegamenuMouseLeave = () => {
|
|
135
|
-
closeMegamenu(200)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Toggle megamenu on click (mobile)
|
|
139
|
-
const toggleMegamenu = (navItem: any) => {
|
|
140
|
-
if (navItem.megamenu) {
|
|
141
|
-
if (activeMegamenuItem.value === navItem.link) {
|
|
142
|
-
closeMegamenu()
|
|
143
|
-
} else {
|
|
144
|
-
openMegamenu(navItem.link)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Body scroll lock for mobile menu
|
|
150
|
-
const lockBodyScroll = () => {
|
|
151
|
-
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
|
|
152
|
-
document.body.style.overflow = 'hidden'
|
|
153
|
-
document.body.style.position = 'fixed'
|
|
154
|
-
document.body.style.width = '100%'
|
|
155
|
-
document.body.style.top = '0'
|
|
156
|
-
if (scrollbarWidth > 0) {
|
|
157
|
-
document.body.style.paddingRight = `${scrollbarWidth}px`
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const unlockBodyScroll = () => {
|
|
162
|
-
document.body.style.overflow = ''
|
|
163
|
-
document.body.style.position = ''
|
|
164
|
-
document.body.style.width = ''
|
|
165
|
-
document.body.style.top = ''
|
|
166
|
-
document.body.style.paddingRight = ''
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Toggle mobile menu
|
|
170
|
-
const toggleMobileMenu = () => {
|
|
171
|
-
mobileMenuOpen.value = !mobileMenuOpen.value
|
|
172
|
-
if (mobileMenuOpen.value) {
|
|
173
|
-
lockBodyScroll()
|
|
174
|
-
expandedMobileItem.value = null
|
|
175
|
-
} else {
|
|
176
|
-
unlockBodyScroll()
|
|
177
|
-
expandedMobileItem.value = null
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Easter egg: right-click logo to open Google Drive
|
|
182
|
-
const handleLogoRightClick = (event: MouseEvent) => {
|
|
183
|
-
event.preventDefault()
|
|
184
|
-
window.open('https://drive.google.com/drive/folders/19FVA6toa2elRXRXO16wrmLn1Rsbhj2Te', '_blank')
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Toggle accordion item in mobile menu
|
|
188
|
-
const toggleMobileAccordion = (navItemLink: string) => {
|
|
189
|
-
if (expandedMobileItem.value === navItemLink) {
|
|
190
|
-
expandedMobileItem.value = null
|
|
191
|
-
} else {
|
|
192
|
-
expandedMobileItem.value = navItemLink
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Close mobile menu
|
|
197
|
-
const closeMobileMenu = () => {
|
|
198
|
-
mobileMenuOpen.value = false
|
|
199
|
-
expandedMobileItem.value = null
|
|
200
|
-
unlockBodyScroll()
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Focus trap for mobile menu
|
|
204
|
-
const focusTrap = {
|
|
205
|
-
firstFocusableEl: null as HTMLElement | null,
|
|
206
|
-
lastFocusableEl: null as HTMLElement | null,
|
|
207
|
-
|
|
208
|
-
activate: () => {
|
|
209
|
-
const mobileMenu = document.getElementById('mobile-menu')
|
|
210
|
-
if (!mobileMenu) return
|
|
211
|
-
|
|
212
|
-
const focusableElements = mobileMenu.querySelectorAll(
|
|
213
|
-
'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
if (focusableElements.length === 0) return
|
|
217
|
-
|
|
218
|
-
focusTrap.firstFocusableEl = focusableElements[0] as HTMLElement
|
|
219
|
-
focusTrap.lastFocusableEl = focusableElements[focusableElements.length - 1] as HTMLElement
|
|
220
|
-
|
|
221
|
-
focusTrap.firstFocusableEl?.focus()
|
|
222
|
-
|
|
223
|
-
document.addEventListener('keydown', focusTrap.handleTabKey)
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
deactivate: () => {
|
|
227
|
-
document.removeEventListener('keydown', focusTrap.handleTabKey)
|
|
228
|
-
focusTrap.firstFocusableEl = null
|
|
229
|
-
focusTrap.lastFocusableEl = null
|
|
230
|
-
},
|
|
231
|
-
|
|
232
|
-
handleTabKey: (e: KeyboardEvent) => {
|
|
233
|
-
if (e.key !== 'Tab') return
|
|
234
|
-
|
|
235
|
-
if (e.shiftKey) {
|
|
236
|
-
if (document.activeElement === focusTrap.firstFocusableEl) {
|
|
237
|
-
focusTrap.lastFocusableEl?.focus()
|
|
238
|
-
e.preventDefault()
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
if (document.activeElement === focusTrap.lastFocusableEl) {
|
|
242
|
-
focusTrap.firstFocusableEl?.focus()
|
|
243
|
-
e.preventDefault()
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Activate/deactivate focus trap when mobile menu opens/closes
|
|
250
|
-
watch(mobileMenuOpen, (isOpen) => {
|
|
251
|
-
if (isOpen) {
|
|
252
|
-
nextTick(() => {
|
|
253
|
-
focusTrap.activate()
|
|
254
|
-
})
|
|
255
|
-
} else {
|
|
256
|
-
focusTrap.deactivate()
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
onMounted(() => {
|
|
261
|
-
document.addEventListener('click', handleClickOutside)
|
|
262
|
-
document.addEventListener('keydown', handleKeydown)
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
onUnmounted(() => {
|
|
266
|
-
document.removeEventListener('click', handleClickOutside)
|
|
267
|
-
document.removeEventListener('keydown', handleKeydown)
|
|
268
|
-
unlockBodyScroll()
|
|
269
|
-
if (hoverTimeout.value) {
|
|
270
|
-
clearTimeout(hoverTimeout.value)
|
|
271
|
-
}
|
|
272
|
-
})
|
|
273
|
-
</script>
|
|
274
|
-
|
|
275
|
-
<template>
|
|
276
|
-
<div class="header-wrapper z-50 relative bg-white dark:bg-primary" ref="megamenuRef">
|
|
277
|
-
<header class="wrapper px-6 py-5 lg:py-7 flex items-center justify-between relative z-[1000]">
|
|
278
|
-
<div class="flex items-center gap-10">
|
|
279
|
-
<a href="/" @contextmenu="handleLogoRightClick">
|
|
280
|
-
<img class="h-4 block dark:hidden" src="@assets/voidzero/logo.svg" alt="VoidZero"/>
|
|
281
|
-
<img class="h-4 hidden dark:block" src="@assets/voidzero/logo-light.svg" alt="VoidZero"/>
|
|
282
|
-
</a>
|
|
283
|
-
|
|
284
|
-
<!-- Desktop navigation - hidden on mobile -->
|
|
285
|
-
<nav class="nav-container hidden md:block">
|
|
286
|
-
<ul class="nav">
|
|
287
|
-
<li
|
|
288
|
-
v-for="navItem in nav"
|
|
289
|
-
:key="navItem.link"
|
|
290
|
-
@mouseenter="handleNavItemMouseEnter(navItem)"
|
|
291
|
-
@mouseleave="handleNavItemMouseLeave"
|
|
292
|
-
class="inline-block px-5"
|
|
293
|
-
>
|
|
294
|
-
<a
|
|
295
|
-
:class="{ active: route.path === navItem.link }"
|
|
296
|
-
:href="navItem.link"
|
|
297
|
-
:target="navItem.link?.startsWith('http') ? '_blank' : navItem.target"
|
|
298
|
-
:rel="navItem.link?.startsWith('http') ? 'noopener noreferrer' : undefined"
|
|
299
|
-
:aria-expanded="navItem.megamenu && activeMegamenuItem === navItem.link ? 'true' : undefined"
|
|
300
|
-
:aria-haspopup="navItem.megamenu ? 'true' : undefined"
|
|
301
|
-
:aria-controls="navItem.megamenu ? `megamenu-${navItem.link}` : undefined"
|
|
302
|
-
@click="navItem.megamenu && ($event.preventDefault(), toggleMegamenu(navItem))"
|
|
303
|
-
>
|
|
304
|
-
{{ navItem.text }}
|
|
305
|
-
<svg
|
|
306
|
-
v-if="navItem.megamenu"
|
|
307
|
-
class="inline-block ml-1 size-3 transition-transform"
|
|
308
|
-
:class="{ 'rotate-180': activeMegamenuItem === navItem.link }"
|
|
309
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
310
|
-
viewBox="0 0 12 7"
|
|
311
|
-
fill="currentColor"
|
|
312
|
-
aria-hidden="true"
|
|
313
|
-
>
|
|
314
|
-
<path d="M1.41 0L6 4.58 10.59 0 12 1.42l-6 6-6-6z"/>
|
|
315
|
-
</svg>
|
|
316
|
-
</a>
|
|
317
|
-
</li>
|
|
318
|
-
</ul>
|
|
319
|
-
</nav>
|
|
320
|
-
</div>
|
|
321
|
-
|
|
322
|
-
<div class="flex items-center gap-6">
|
|
323
|
-
<a href="https://github.com/voidzero-dev" target="_blank" rel="noopener noreferrer"
|
|
324
|
-
class="hidden lg:flex gap-1.5 items-center">
|
|
325
|
-
<img src="@assets/social/github.svg" alt="Star on GitHub" class="size-6 block dark:hidden">
|
|
326
|
-
<img src="@assets/social/github-light.svg" alt="Star on GitHub" class="size-6 hidden dark:block">
|
|
327
|
-
<span class="text-primary dark:text-grey font-mono text-sm font-medium">{{ formattedStars }}</span>
|
|
328
|
-
</a>
|
|
329
|
-
<a href="https://viteplus.dev" target="_blank" rel="noopener noreferrer" class="button hidden md:block">
|
|
330
|
-
Explore Vite+
|
|
331
|
-
</a>
|
|
332
|
-
|
|
333
|
-
<!-- Mobile hamburger/close button - Right aligned -->
|
|
334
|
-
<button
|
|
335
|
-
@click="toggleMobileMenu"
|
|
336
|
-
:aria-expanded="mobileMenuOpen"
|
|
337
|
-
aria-controls="mobile-menu"
|
|
338
|
-
aria-label="Toggle navigation menu"
|
|
339
|
-
class="md:hidden p-2 -mr-2 text-primary dark:text-white hover:opacity-70 transition-opacity cursor-pointer"
|
|
340
|
-
type="button"
|
|
341
|
-
>
|
|
342
|
-
<svg v-if="!mobileMenuOpen" class="size-6 block dark:hidden" viewBox="0 0 18 8" xmlns="http://www.w3.org/2000/svg">
|
|
343
|
-
<path d="M0 0.75H18" stroke="#08060D" stroke-width="1.5"/>
|
|
344
|
-
<path d="M0 6.75H18" stroke="#08060D" stroke-width="1.5"/>
|
|
345
|
-
</svg>
|
|
346
|
-
<svg v-if="!mobileMenuOpen" class="size-6 hidden dark:block" viewBox="0 0 18 8" xmlns="http://www.w3.org/2000/svg">
|
|
347
|
-
<path d="M0 0.75H18" stroke="#FFFFFF" stroke-width="1.5"/>
|
|
348
|
-
<path d="M0 6.75H18" stroke="#FFFFFF" stroke-width="1.5"/>
|
|
349
|
-
</svg>
|
|
350
|
-
|
|
351
|
-
</button>
|
|
352
|
-
</div>
|
|
353
|
-
</header>
|
|
354
|
-
|
|
355
|
-
<!-- Mobile Menu Overlay - Full Screen -->
|
|
356
|
-
<div
|
|
357
|
-
v-if="mobileMenuOpen"
|
|
358
|
-
id="mobile-menu"
|
|
359
|
-
role="dialog"
|
|
360
|
-
aria-modal="true"
|
|
361
|
-
aria-label="Mobile navigation menu"
|
|
362
|
-
data-theme="dark"
|
|
363
|
-
class="md:hidden fixed inset-0 z-[1001] bg-primary"
|
|
364
|
-
>
|
|
365
|
-
<section class="wrapper animate-fade-in">
|
|
366
|
-
|
|
367
|
-
<!-- Internal Header with Logo and Close Button -->
|
|
368
|
-
<div class="w-full pl-5 pr-7 py-5 lg:py-7 flex items-center justify-between">
|
|
369
|
-
<a href="/">
|
|
370
|
-
<img class="h-4" src="@assets/voidzero/logo-light.svg" alt="VoidZero"/>
|
|
371
|
-
</a>
|
|
372
|
-
<button
|
|
373
|
-
@click="closeMobileMenu"
|
|
374
|
-
aria-label="Close navigation menu"
|
|
375
|
-
class="p-2 -mr-2 text-white hover:opacity-70 transition-opacity"
|
|
376
|
-
type="button"
|
|
377
|
-
>
|
|
378
|
-
<svg
|
|
379
|
-
class="size-6 cursor-pointer"
|
|
380
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
381
|
-
fill="none"
|
|
382
|
-
viewBox="0 0 24 24"
|
|
383
|
-
stroke="currentColor"
|
|
384
|
-
aria-hidden="true"
|
|
385
|
-
>
|
|
386
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
387
|
-
</svg>
|
|
388
|
-
</button>
|
|
389
|
-
</div>
|
|
390
|
-
|
|
391
|
-
<!-- Scrollable content container -->
|
|
392
|
-
<div
|
|
393
|
-
class="overflow-y-auto flex flex-col [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
|
|
394
|
-
style="height: calc(100vh - 88px);">
|
|
395
|
-
<!-- Navigation Items - Top Section -->
|
|
396
|
-
<nav class="flex-1 w-full pt-6 pb-8">
|
|
397
|
-
<ul class="space-y-1">
|
|
398
|
-
<li v-for="navItem in nav" :key="navItem.link">
|
|
399
|
-
<!-- Nav items with megamenu - Accordion trigger -->
|
|
400
|
-
<template v-if="navItem.megamenu">
|
|
401
|
-
<button
|
|
402
|
-
@click="toggleMobileAccordion(navItem.link)"
|
|
403
|
-
:aria-expanded="expandedMobileItem === navItem.link"
|
|
404
|
-
:aria-controls="`mobile-megamenu-${navItem.link}`"
|
|
405
|
-
class="w-full text-left py-3 px-4 text-lg font-sans text-white flex items-center justify-between cursor-pointer"
|
|
406
|
-
>
|
|
407
|
-
<span>{{ navItem.text }}</span>
|
|
408
|
-
<svg class="size-4 transition-transform duration-200"
|
|
409
|
-
:class="{ 'rotate-180': expandedMobileItem === navItem.link }" viewBox="0 0 15 8" fill="none"
|
|
410
|
-
xmlns="http://www.w3.org/2000/svg">
|
|
411
|
-
<path d="M13.8638 0.530325L7.1971 7.19699L0.530438 0.530324" stroke="#F4F3EC" stroke-width="1.5"
|
|
412
|
-
stroke-linejoin="round"/>
|
|
413
|
-
</svg>
|
|
414
|
-
|
|
415
|
-
</button>
|
|
416
|
-
|
|
417
|
-
<!-- Accordion Content - Megamenu (instant, no transition) -->
|
|
418
|
-
<Transition>
|
|
419
|
-
<div
|
|
420
|
-
v-if="expandedMobileItem === navItem.link"
|
|
421
|
-
:id="`mobile-megamenu-${navItem.link}`"
|
|
422
|
-
class="pb-4 pt-2"
|
|
423
|
-
>
|
|
424
|
-
<Megamenu
|
|
425
|
-
:featured-articles="props.featuredArticles"
|
|
426
|
-
:on-link-click="closeMobileMenu"
|
|
427
|
-
/>
|
|
428
|
-
</div>
|
|
429
|
-
</Transition>
|
|
430
|
-
</template>
|
|
431
|
-
|
|
432
|
-
<!-- Regular nav items without megamenu -->
|
|
433
|
-
<template v-else>
|
|
434
|
-
<a
|
|
435
|
-
:href="navItem.link"
|
|
436
|
-
@click="closeMobileMenu"
|
|
437
|
-
:class="{ 'bg-white/10': route.path === navItem.link }"
|
|
438
|
-
class="block py-3 px-4 text-lg font-sans text-white"
|
|
439
|
-
>
|
|
440
|
-
{{ navItem.text }}
|
|
441
|
-
</a>
|
|
442
|
-
</template>
|
|
443
|
-
</li>
|
|
444
|
-
</ul>
|
|
445
|
-
</nav>
|
|
446
|
-
|
|
447
|
-
<!-- Bottom Section - CTA and Social -->
|
|
448
|
-
<div class="w-full py-12 border-t border-nickel relative tick-left tick-right mt-auto">
|
|
449
|
-
<div class="space-y-12">
|
|
450
|
-
<!-- CTA Button -->
|
|
451
|
-
<div class="px-6">
|
|
452
|
-
<a
|
|
453
|
-
href="https://viteplus.dev"
|
|
454
|
-
target="_blank"
|
|
455
|
-
rel="noopener noreferrer"
|
|
456
|
-
class="button button--primary button--white block text-center bg-white text-primary hover:bg-white/90"
|
|
457
|
-
@click="closeMobileMenu"
|
|
458
|
-
>
|
|
459
|
-
<span>Explore Vite+</span>
|
|
460
|
-
</a>
|
|
461
|
-
</div>
|
|
462
|
-
|
|
463
|
-
<!-- Divider -->
|
|
464
|
-
<div class="border-t border-nickel tick-left tick-right relative"></div>
|
|
465
|
-
|
|
466
|
-
<!-- Social Icons -->
|
|
467
|
-
<div class="flex items-center justify-center gap-4 pb-12">
|
|
468
|
-
<a
|
|
469
|
-
href="https://github.com/voidzero-dev"
|
|
470
|
-
target="_blank"
|
|
471
|
-
rel="noopener noreferrer"
|
|
472
|
-
class="hover:opacity-70 transition-opacity"
|
|
473
|
-
@click="closeMobileMenu"
|
|
474
|
-
>
|
|
475
|
-
<img src="@assets/social/github-light.svg" alt="GitHub" class="size-6">
|
|
476
|
-
</a>
|
|
477
|
-
<a
|
|
478
|
-
href="https://bsky.app/profile/voidzero.dev"
|
|
479
|
-
target="_blank"
|
|
480
|
-
rel="noopener noreferrer"
|
|
481
|
-
class="hover:opacity-70 transition-opacity"
|
|
482
|
-
@click="closeMobileMenu"
|
|
483
|
-
>
|
|
484
|
-
<img src="@assets/social/bluesky-light.svg" alt="Bluesky" class="size-6">
|
|
485
|
-
</a>
|
|
486
|
-
<a
|
|
487
|
-
href="https://x.com/voidzerodev"
|
|
488
|
-
target="_blank"
|
|
489
|
-
rel="noopener noreferrer"
|
|
490
|
-
class="hover:opacity-70 transition-opacity"
|
|
491
|
-
@click="closeMobileMenu"
|
|
492
|
-
>
|
|
493
|
-
<img src="@assets/social/twitter-light.svg" alt="X" class="size-6">
|
|
494
|
-
</a>
|
|
495
|
-
</div>
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
</section>
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
<!-- Desktop Megamenu Panel - Full Width (Hidden on mobile) -->
|
|
503
|
-
<Transition
|
|
504
|
-
enter-active-class="transition-all duration-200 ease-out"
|
|
505
|
-
enter-from-class="opacity-0 -translate-y-2"
|
|
506
|
-
enter-to-class="opacity-100 translate-y-0"
|
|
507
|
-
leave-active-class="transition-all duration-150 ease-in"
|
|
508
|
-
leave-from-class="opacity-100 translate-y-0"
|
|
509
|
-
leave-to-class="opacity-0 -translate-y-2"
|
|
510
|
-
>
|
|
511
|
-
<div
|
|
512
|
-
v-if="megamenuOpen && !mobileMenuOpen"
|
|
513
|
-
:id="`megamenu-${activeMegamenuItem}`"
|
|
514
|
-
role="region"
|
|
515
|
-
aria-label="Mega menu"
|
|
516
|
-
class="hidden md:block absolute top-full left-0 right-0 w-full z-[1000] pt-10 -mt-10"
|
|
517
|
-
@mouseenter="handleMegamenuMouseEnter"
|
|
518
|
-
@mouseleave="handleMegamenuMouseLeave"
|
|
519
|
-
>
|
|
520
|
-
<div class="wrapper wrapper--ticks border-t border-b bg-white dark:bg-primary megamenu-shadow">
|
|
521
|
-
<div class="mx-28 border-l border-r border-stroke dark:border-nickel">
|
|
522
|
-
<Megamenu
|
|
523
|
-
:featured-articles="props.featuredArticles"
|
|
524
|
-
/>
|
|
525
|
-
</div>
|
|
526
|
-
</div>
|
|
527
|
-
</div>
|
|
528
|
-
</Transition>
|
|
529
|
-
</div>
|
|
530
|
-
</template>
|
|
531
|
-
|
|
532
|
-
<style scoped>
|
|
533
|
-
.megamenu-shadow {
|
|
534
|
-
box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px;
|
|
535
|
-
animation: shadowFadeIn 0.3s ease-out forwards;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
@keyframes shadowFadeIn {
|
|
539
|
-
from {
|
|
540
|
-
box-shadow: rgba(50, 50, 93, 0.1) 0px 25px 50px -20px, rgba(0, 0, 0, 0.15) 0px 15px 30px -30px;
|
|
541
|
-
}
|
|
542
|
-
to {
|
|
543
|
-
box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
@keyframes fadeIn {
|
|
548
|
-
from {
|
|
549
|
-
opacity: 0;
|
|
550
|
-
}
|
|
551
|
-
to {
|
|
552
|
-
opacity: 1;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
.animate-fade-in {
|
|
557
|
-
animation: fadeIn 300ms ease-out 100ms forwards;
|
|
558
|
-
opacity: 0;
|
|
559
|
-
}
|
|
560
|
-
</style>
|