|
1 | 1 | <script setup lang="ts"> |
2 | | -import { useData, useRoute } from 'vitepress'; |
| 2 | +import { ref, onMounted, onUnmounted } from 'vue'; |
3 | 3 |
|
4 | 4 | const nav = [{ text: 'Docs', link: '/vite/guide' }]; |
| 5 | +
|
| 6 | +// Mobile menu state |
| 7 | +const mobileMenuOpen = ref(false); |
| 8 | +const expandedMobileItem = ref<string | null>(null); |
| 9 | +
|
| 10 | +// Body scroll lock for mobile menu |
| 11 | +const lockBodyScroll = () => { |
| 12 | + const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; |
| 13 | + document.body.style.overflow = 'hidden'; |
| 14 | + document.body.style.position = 'fixed'; |
| 15 | + document.body.style.width = '100%'; |
| 16 | + document.body.style.top = '0'; |
| 17 | + if (scrollbarWidth > 0) { |
| 18 | + document.body.style.paddingRight = `${scrollbarWidth}px`; |
| 19 | + } |
| 20 | +}; |
| 21 | +
|
| 22 | +const unlockBodyScroll = () => { |
| 23 | + document.body.style.overflow = ''; |
| 24 | + document.body.style.position = ''; |
| 25 | + document.body.style.width = ''; |
| 26 | + document.body.style.top = ''; |
| 27 | + document.body.style.paddingRight = ''; |
| 28 | +}; |
| 29 | +
|
| 30 | +// Close mobile menu |
| 31 | +const closeMobileMenu = () => { |
| 32 | + mobileMenuOpen.value = false; |
| 33 | + expandedMobileItem.value = null; |
| 34 | + unlockBodyScroll(); |
| 35 | +}; |
| 36 | +
|
| 37 | +// Handle keyboard navigation |
| 38 | +const handleKeydown = (e: KeyboardEvent) => { |
| 39 | + if (e.key === 'Escape') { |
| 40 | + if (mobileMenuOpen.value) { |
| 41 | + closeMobileMenu(); |
| 42 | + } |
| 43 | + } |
| 44 | +}; |
| 45 | +
|
| 46 | +// Toggle mobile menu |
| 47 | +const toggleMobileMenu = () => { |
| 48 | + mobileMenuOpen.value = !mobileMenuOpen.value; |
| 49 | + if (mobileMenuOpen.value) { |
| 50 | + lockBodyScroll(); |
| 51 | + expandedMobileItem.value = null; |
| 52 | + } else { |
| 53 | + unlockBodyScroll(); |
| 54 | + expandedMobileItem.value = null; |
| 55 | + } |
| 56 | +}; |
| 57 | +
|
| 58 | +onMounted(() => { |
| 59 | + document.addEventListener('keydown', handleKeydown); |
| 60 | +}); |
| 61 | +
|
| 62 | +onUnmounted(() => { |
| 63 | + document.removeEventListener('keydown', handleKeydown); |
| 64 | + unlockBodyScroll(); |
| 65 | +}); |
5 | 66 | </script> |
6 | 67 |
|
7 | 68 | <template> |
@@ -53,16 +114,203 @@ const nav = [{ text: 'Docs', link: '/vite/guide' }]; |
53 | 114 | <img class="h-3" src="@assets/logos/voidzero-dark.svg" alt="VoidZero" /> |
54 | 115 | </a> |
55 | 116 | </span> |
56 | | - <a |
57 | | - href="https://tally.so/r/nGWebL" |
58 | | - target="_blank" |
59 | | - rel="noopener noreferrer" |
60 | | - class="button hidden sm:block" |
| 117 | + <a href="/vite/guide" target="_self" class="button hidden md:block"> Get started </a> |
| 118 | + |
| 119 | + <!-- Mobile hamburger/close button - Right aligned --> |
| 120 | + <button |
| 121 | + @click="toggleMobileMenu" |
| 122 | + :aria-expanded="mobileMenuOpen" |
| 123 | + aria-controls="mobile-menu" |
| 124 | + aria-label="Toggle navigation menu" |
| 125 | + class="md:hidden p-2 -mr-2 text-primary dark:text-white hover:opacity-70 transition-opacity cursor-pointer" |
| 126 | + type="button" |
61 | 127 | > |
62 | | - Join early access |
63 | | - </a> |
| 128 | + <svg |
| 129 | + v-if="!mobileMenuOpen" |
| 130 | + class="size-6 block dark:hidden" |
| 131 | + viewBox="0 0 18 8" |
| 132 | + xmlns="http://www.w3.org/2000/svg" |
| 133 | + > |
| 134 | + <path d="M0 0.75H18" stroke="#08060D" stroke-width="1.5" /> |
| 135 | + <path d="M0 6.75H18" stroke="#08060D" stroke-width="1.5" /> |
| 136 | + </svg> |
| 137 | + <svg |
| 138 | + v-if="!mobileMenuOpen" |
| 139 | + class="size-6 hidden dark:block" |
| 140 | + viewBox="0 0 18 8" |
| 141 | + xmlns="http://www.w3.org/2000/svg" |
| 142 | + > |
| 143 | + <path d="M0 0.75H18" stroke="#FFFFFF" stroke-width="1.5" /> |
| 144 | + <path d="M0 6.75H18" stroke="#FFFFFF" stroke-width="1.5" /> |
| 145 | + </svg> |
| 146 | + </button> |
64 | 147 | </div> |
65 | 148 | </header> |
| 149 | + |
| 150 | + <!-- Mobile Menu Overlay - Full Screen --> |
| 151 | + <div |
| 152 | + v-if="mobileMenuOpen" |
| 153 | + id="mobile-menu" |
| 154 | + role="dialog" |
| 155 | + aria-modal="true" |
| 156 | + aria-label="Mobile navigation menu" |
| 157 | + data-theme="dark" |
| 158 | + class="md:hidden fixed inset-0 z-[1001] bg-primary" |
| 159 | + > |
| 160 | + <section class="wrapper animate-fade-in"> |
| 161 | + <!-- Internal Header with Logo and Close Button --> |
| 162 | + <div class="w-full pl-5 pr-7 py-5 lg:py-7 flex items-center justify-between"> |
| 163 | + <a href="/"> |
| 164 | + <img class="h-4" src="@assets/logos/viteplus-light.svg" alt="Vite+" /> |
| 165 | + </a> |
| 166 | + <button |
| 167 | + @click="closeMobileMenu" |
| 168 | + aria-label="Close navigation menu" |
| 169 | + class="p-2 -mr-2 text-white hover:opacity-70 transition-opacity" |
| 170 | + type="button" |
| 171 | + > |
| 172 | + <svg |
| 173 | + class="size-6 cursor-pointer" |
| 174 | + xmlns="http://www.w3.org/2000/svg" |
| 175 | + fill="none" |
| 176 | + viewBox="0 0 24 24" |
| 177 | + stroke="currentColor" |
| 178 | + aria-hidden="true" |
| 179 | + > |
| 180 | + <path |
| 181 | + stroke-linecap="round" |
| 182 | + stroke-linejoin="round" |
| 183 | + stroke-width="2" |
| 184 | + d="M6 18L18 6M6 6l12 12" |
| 185 | + /> |
| 186 | + </svg> |
| 187 | + </button> |
| 188 | + </div> |
| 189 | + |
| 190 | + <!-- Scrollable content container --> |
| 191 | + <div |
| 192 | + class="overflow-y-auto flex flex-col [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden" |
| 193 | + style="height: calc(100vh - 88px)" |
| 194 | + > |
| 195 | + <!-- Navigation Items - Top Section --> |
| 196 | + <nav class="flex-1 w-full pt-6 pb-8"> |
| 197 | + <ul class="space-y-1"> |
| 198 | + <li v-for="navItem in nav" :key="navItem.link"> |
| 199 | + <!-- :class="{ 'bg-white/10': route.path === navItem.link }" --> |
| 200 | + <a |
| 201 | + :href="navItem.link" |
| 202 | + @click="closeMobileMenu" |
| 203 | + :target="navItem.link?.startsWith('http') ? '_blank' : '_self'" |
| 204 | + :rel="navItem.link?.startsWith('http') ? 'noopener noreferrer' : undefined" |
| 205 | + class="flex py-3 px-4 text-lg font-sans text-white items-center justify-between" |
| 206 | + > |
| 207 | + {{ navItem.text }} |
| 208 | + <svg |
| 209 | + v-if="navItem.link?.startsWith('http')" |
| 210 | + class="inline-block ml-1 size-4" |
| 211 | + xmlns="http://www.w3.org/2000/svg" |
| 212 | + viewBox="0 0 12 12" |
| 213 | + fill="none" |
| 214 | + aria-hidden="true" |
| 215 | + > |
| 216 | + <path |
| 217 | + d="M2.81802 2.81803L9.18198 2.81803L9.18198 9.18199" |
| 218 | + class="stroke-primary dark:stroke-white" |
| 219 | + stroke-width="1.5" |
| 220 | + /> |
| 221 | + <path |
| 222 | + d="M9.18213 2.81802L2.81817 9.18198" |
| 223 | + class="stroke-primary dark:stroke-white" |
| 224 | + stroke-width="1.5" |
| 225 | + /> |
| 226 | + </svg> |
| 227 | + </a> |
| 228 | + </li> |
| 229 | + </ul> |
| 230 | + </nav> |
| 231 | + |
| 232 | + <!-- Bottom Section - CTA and Social --> |
| 233 | + <div class="w-full py-12 border-t border-nickel relative tick-left tick-right mt-auto"> |
| 234 | + <div class="space-y-12"> |
| 235 | + <!-- CTA Button --> |
| 236 | + <div class="px-6"> |
| 237 | + <a |
| 238 | + href="/vite/guide" |
| 239 | + target="_self" |
| 240 | + class="button button--primary button--white block text-center bg-white text-primary hover:bg-white/90" |
| 241 | + @click="closeMobileMenu" |
| 242 | + > |
| 243 | + <span>Get started</span> |
| 244 | + </a> |
| 245 | + </div> |
| 246 | + |
| 247 | + <!-- Divider --> |
| 248 | + <div class="border-t border-nickel tick-left tick-right relative"></div> |
| 249 | + |
| 250 | + <!-- Social Icons --> |
| 251 | + <div class="flex items-center justify-center gap-4 pb-12"> |
| 252 | + <a |
| 253 | + href="https://github.com/voidzero-dev" |
| 254 | + target="_blank" |
| 255 | + rel="noopener noreferrer" |
| 256 | + class="hover:opacity-70 transition-opacity" |
| 257 | + @click="closeMobileMenu" |
| 258 | + > |
| 259 | + <img src="@assets/social/github-light.svg" alt="GitHub" class="size-6" /> |
| 260 | + </a> |
| 261 | + <a |
| 262 | + href="https://bsky.app/profile/voidzero.dev" |
| 263 | + target="_blank" |
| 264 | + rel="noopener noreferrer" |
| 265 | + class="hover:opacity-70 transition-opacity" |
| 266 | + @click="closeMobileMenu" |
| 267 | + > |
| 268 | + <img src="@assets/social/bluesky-light.svg" alt="Bluesky" class="size-6" /> |
| 269 | + </a> |
| 270 | + <a |
| 271 | + href="https://x.com/voidzerodev" |
| 272 | + target="_blank" |
| 273 | + rel="noopener noreferrer" |
| 274 | + class="hover:opacity-70 transition-opacity" |
| 275 | + @click="closeMobileMenu" |
| 276 | + > |
| 277 | + <img src="@assets/social/twitter-light.svg" alt="X" class="size-6" /> |
| 278 | + </a> |
| 279 | + </div> |
| 280 | + </div> |
| 281 | + </div> |
| 282 | + </div> |
| 283 | + </section> |
| 284 | + </div> |
66 | 285 | </template> |
67 | 286 |
|
68 | | -<style scoped></style> |
| 287 | +<style scoped> |
| 288 | +@keyframes shadowFadeIn { |
| 289 | + from { |
| 290 | + box-shadow: |
| 291 | + rgba(50, 50, 93, 0.1) 0px 25px 50px -20px, |
| 292 | + rgba(0, 0, 0, 0.15) 0px 15px 30px -30px; |
| 293 | + } |
| 294 | +
|
| 295 | + to { |
| 296 | + box-shadow: |
| 297 | + rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, |
| 298 | + rgba(0, 0, 0, 0.3) 0px 30px 60px -30px; |
| 299 | + } |
| 300 | +} |
| 301 | +
|
| 302 | +@keyframes fadeIn { |
| 303 | + from { |
| 304 | + opacity: 0; |
| 305 | + } |
| 306 | +
|
| 307 | + to { |
| 308 | + opacity: 1; |
| 309 | + } |
| 310 | +} |
| 311 | +
|
| 312 | +.animate-fade-in { |
| 313 | + animation: fadeIn 300ms ease-out 100ms forwards; |
| 314 | + opacity: 0; |
| 315 | +} |
| 316 | +</style> |
0 commit comments