@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.
Files changed (79) hide show
  1. package/package.json +7 -6
  2. package/src/aliases.js +14 -0
  3. package/src/vitepress/assets/clients/clickup.svg +5 -0
  4. package/src/vitepress/assets/clients/stripe.svg +3 -0
  5. package/src/vitepress/components/oss/Footer.vue +4 -21
  6. package/src/vitepress/components/oss/Header.vue +82 -180
  7. package/src/vitepress/components/oss/Sponsors.vue +3 -3
  8. package/src/vitepress/components/oss/TopBanner.vue +20 -79
  9. package/src/vitepress/components/oss/TrustedBy.vue +1 -1
  10. package/src/vitepress/components/vite/Community.vue +3 -3
  11. package/src/vitepress/components/vite/FeatureGrid1.vue +63 -0
  12. package/src/vitepress/components/vite/{FeatureGrid.vue → FeatureGrid2.vue} +8 -10
  13. package/src/vitepress/components/vite/Hero.vue +6 -15
  14. package/src/vitepress/components/vitepress-default/VPDocOutlineItem.vue +2 -2
  15. package/src/vitepress/components/vitepress-default/VPFlyout.vue +1 -1
  16. package/src/vitepress/components/vitepress-default/VPMenuLink.vue +1 -1
  17. package/src/vitepress/components/vitepress-default/VPNavBarMenuLink.vue +1 -1
  18. package/src/vitepress/components/vitepress-default/VPSidebarItem.vue +1 -1
  19. package/src/vitepress/components/vitepress-default/VPSocialLink.vue +1 -2
  20. package/src/vitepress/fonts/APK-Protocol-Semi-Bold.woff2 +0 -0
  21. package/src/vitepress/fonts/inter-italic-cyrillic-ext.woff2 +0 -0
  22. package/src/vitepress/fonts/inter-italic-cyrillic.woff2 +0 -0
  23. package/src/vitepress/fonts/inter-italic-greek-ext.woff2 +0 -0
  24. package/src/vitepress/fonts/inter-italic-greek.woff2 +0 -0
  25. package/src/vitepress/fonts/inter-italic-latin-ext.woff2 +0 -0
  26. package/src/vitepress/fonts/inter-italic-latin.woff2 +0 -0
  27. package/src/vitepress/fonts/inter-italic-vietnamese.woff2 +0 -0
  28. package/src/vitepress/fonts/inter-roman-cyrillic-ext.woff2 +0 -0
  29. package/src/vitepress/fonts/inter-roman-cyrillic.woff2 +0 -0
  30. package/src/vitepress/fonts/inter-roman-greek-ext.woff2 +0 -0
  31. package/src/vitepress/fonts/inter-roman-greek.woff2 +0 -0
  32. package/src/vitepress/fonts/inter-roman-latin-ext.woff2 +0 -0
  33. package/src/vitepress/fonts/inter-roman-latin.woff2 +0 -0
  34. package/src/vitepress/fonts/inter-roman-vietnamese.woff2 +0 -0
  35. package/src/vitepress/index.ts +64 -230
  36. package/src/vitepress/layouts/VPLayout.vue +2 -17
  37. package/src/vitepress/styles/tokens.css +194 -10
  38. package/src/vitepress/types/theme-context.ts +33 -0
  39. package/src/vitepress/assets/clients/beehiiv.svg +0 -30
  40. package/src/vitepress/assets/clients/excalidraw.svg +0 -82
  41. package/src/vitepress/assets/clients/get-your-guide.svg +0 -1
  42. package/src/vitepress/assets/clients/posthog.svg +0 -1
  43. package/src/vitepress/assets/clients/ramp.svg +0 -1
  44. package/src/vitepress/assets/clients/shopee.svg +0 -55
  45. package/src/vitepress/components/vite/FeaturePanel1.vue +0 -41
  46. package/src/vitepress/components/vite/FeaturePanel2.vue +0 -37
  47. package/src/vitepress/components/vite/FeaturePanel3.vue +0 -43
  48. package/src/vitepress/components/vite/FeaturePanel4.vue +0 -46
  49. package/src/vitepress/components/voidzero/Footer.vue +0 -65
  50. package/src/vitepress/components/voidzero/Header.vue +0 -560
  51. package/src/vitepress/components/voidzero/Megamenu.vue +0 -190
  52. package/src/vitepress/components/voidzero/about/CareerCTA.vue +0 -56
  53. package/src/vitepress/components/voidzero/about/Hero.vue +0 -206
  54. package/src/vitepress/components/voidzero/about/Investors.vue +0 -112
  55. package/src/vitepress/components/voidzero/about/TeamGrid.vue +0 -161
  56. package/src/vitepress/components/voidzero/about/TeamSectionHeading.vue +0 -13
  57. package/src/vitepress/components/voidzero/blog/BlogArchive.vue +0 -223
  58. package/src/vitepress/components/voidzero/blog/BlogSingleContent.vue +0 -364
  59. package/src/vitepress/components/voidzero/blog/BlogSingleHero.vue +0 -113
  60. package/src/vitepress/components/voidzero/blog/BlogSingleRelated.vue +0 -92
  61. package/src/vitepress/components/voidzero/blog/FeaturedArticles.vue +0 -146
  62. package/src/vitepress/components/voidzero/blog/types.ts +0 -56
  63. package/src/vitepress/components/voidzero/home/CaseStudySlider.vue +0 -235
  64. package/src/vitepress/components/voidzero/home/CustomersSectionHeading.vue +0 -5
  65. package/src/vitepress/components/voidzero/home/GitHubStats.vue +0 -27
  66. package/src/vitepress/components/voidzero/home/Hero.vue +0 -69
  67. package/src/vitepress/components/voidzero/home/Investors.vue +0 -30
  68. package/src/vitepress/components/voidzero/home/NewsletterCTA.vue +0 -23
  69. package/src/vitepress/components/voidzero/home/OpenSourceSectionHeading.vue +0 -6
  70. package/src/vitepress/components/voidzero/home/OpenSourceSectionProjects.vue +0 -419
  71. package/src/vitepress/components/voidzero/home/Resources.vue +0 -144
  72. package/src/vitepress/components/voidzero/home/Statistics.vue +0 -507
  73. package/src/vitepress/components/voidzero/home/StatisticsSectionHeading.vue +0 -5
  74. package/src/vitepress/components/voidzero/home/TeamCTA.vue +0 -17
  75. package/src/vitepress/components/voidzero/home/TrustedBy.vue +0 -248
  76. package/src/vitepress/components/voidzero/home/VitePlusSectionFeatures.vue +0 -55
  77. package/src/vitepress/components/voidzero/home/VitePlusSectionHeading.vue +0 -17
  78. package/src/vitepress/fonts/KHTeka-Medium.woff2 +0 -0
  79. package/src/vitepress/fonts/KHTeka-Regular.woff2 +0 -0
@@ -1,161 +0,0 @@
1
- <script setup lang="ts">
2
- import githubIcon from '@assets/social/github.svg'
3
- import twitterIcon from '@assets/social/twitter.svg'
4
- import blueskyIcon from '@assets/social/bluesky.svg'
5
- import discordIcon from '@assets/social/discord.svg'
6
-
7
- interface Member {
8
- avatar: string
9
- name: string
10
- title: string
11
- desc?: string
12
- handle: string
13
- links: Array<{ icon: 'github' | 'x' | 'bluesky' | 'discord', link: string }>
14
- }
15
-
16
- const props = defineProps<{
17
- members: Member[]
18
- }>()
19
-
20
- const iconMap = {
21
- github: githubIcon,
22
- x: twitterIcon,
23
- bluesky: blueskyIcon,
24
- discord: discordIcon,
25
- }
26
-
27
- // Helper to determine which borders a card needs based on its position
28
- const getBorderClasses = (index: number) => {
29
- const classes: string[] = []
30
- const position = index + 1 // 1-based
31
- const total = props.members.length
32
-
33
- // RIGHT BORDERS: remove from last item in each row per breakpoint
34
- // Mobile (1 col): no right borders
35
- classes.push('border-r-0')
36
-
37
- // SM (2 cols): border-r on odd items (1st, 3rd, 5th...)
38
- if (position % 2 !== 0) classes.push('sm:border-r')
39
-
40
- // LG (3 cols): border-r on all except every 3rd (3rd, 6th, 9th...)
41
- if (position % 3 !== 0) classes.push('lg:border-r')
42
- else classes.push('lg:border-r-0')
43
-
44
- // XL (4 cols): border-r on all except every 4th (4th, 8th, 12th...)
45
- if (position % 4 !== 0) classes.push('xl:border-r')
46
- else classes.push('xl:border-r-0')
47
-
48
- // TOP BORDERS: remove from first row per breakpoint
49
- // Mobile (1 col): top border on all except 1st
50
- if (position > 1) classes.push('border-t')
51
-
52
- // SM (2 cols): top border on all except 1st row (positions 1-2)
53
- if (position > 2) classes.push('sm:border-t')
54
- else classes.push('sm:border-t-0')
55
-
56
- // LG (3 cols): top border on all except 1st row (positions 1-3)
57
- if (position > 3) classes.push('lg:border-t')
58
- else classes.push('lg:border-t-0')
59
-
60
- // XL (4 cols): top border on all except 1st row (positions 1-4)
61
- if (position > 4) classes.push('xl:border-t')
62
- else classes.push('xl:border-t-0')
63
-
64
- // BOTTOM BORDERS: remove from last row per breakpoint
65
- // Mobile (1 col): bottom border on all except last
66
- if (position < total) classes.push('border-b')
67
-
68
- // SM (2 cols): bottom border on all except last row
69
- const lastRowStartSm = Math.ceil(total / 2) * 2 - 1 // Start of last row in 2-col
70
- if (position <= total - 2 || position < lastRowStartSm) classes.push('sm:border-b')
71
- else classes.push('sm:border-b-0')
72
-
73
- // LG (3 cols): bottom border on all except last row
74
- const lastRowStartLg = Math.ceil(total / 3) * 3 - 2
75
- if (position <= total - 3 || position < lastRowStartLg) classes.push('lg:border-b')
76
- else classes.push('lg:border-b-0')
77
-
78
- // XL (4 cols): bottom border on all except last row
79
- const lastRowStartXl = Math.ceil(total / 4) * 4 - 3
80
- if (position <= total - 4 || position < lastRowStartXl) classes.push('xl:border-b')
81
- else classes.push('xl:border-b-0')
82
-
83
- return classes.join(' ')
84
- }
85
-
86
- const formatDescription = (desc: string): string => {
87
- // Format as line comments: "Creator of Vue.js & Vite" -> "// Creator of\n// Vue.js & Vite"
88
- const words = desc.split(' ')
89
- const lines: string[] = []
90
- let currentLine = ''
91
-
92
- // Group words into lines (roughly 2-3 words per line for readability)
93
- words.forEach((word, i) => {
94
- if (i % 3 === 0 && i > 0) {
95
- lines.push(currentLine.trim())
96
- currentLine = word
97
- } else {
98
- currentLine += (currentLine ? ' ' : '') + word
99
- }
100
- })
101
- if (currentLine) lines.push(currentLine.trim())
102
-
103
- // Build comment format
104
- return lines.map(line => `// ${line}`).join('\n')
105
- }
106
- </script>
107
-
108
- <template>
109
- <section id="team" class="wrapper wrapper--ticks border-t">
110
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-10">
111
- <div
112
- v-for="(member, index) in members"
113
- :key="index"
114
- class="border-stroke flex flex-col"
115
- :class="getBorderClasses(index)"
116
- >
117
- <!-- Card content -->
118
- <div class="flex flex-col gap-5 pt-4 md:pt-8 pb-4 flex-1">
119
- <!-- Header: Avatar + Name/Title -->
120
- <div class="px-8 flex gap-6">
121
- <div class="w-14 h-14 rounded-[10px] bg-violet-600/20 overflow-hidden flex-shrink-0">
122
- <img :src="member.avatar" :alt="member.name" class="w-full h-full object-cover" />
123
- </div>
124
- <div class="flex flex-col gap-0.5 flex-1 min-w-0">
125
- <h5 class="text-primary md:text-lg font-medium truncate">{{ member.name }}</h5>
126
- <p class="text-nickel text-base">{{ member.title }}</p>
127
- </div>
128
- </div>
129
-
130
- <!-- Description (comment format) -->
131
- <div class="px-8 hidden md:block min-h-[3rem]">
132
- <pre v-if="member.desc" class="font-mono text-grey text-xs uppercase whitespace-pre-wrap leading-tight">{{ formatDescription(member.desc) }}</pre>
133
- </div>
134
- </div>
135
-
136
- <!-- Footer: Social links + Handle -->
137
- <div class="px-6 py-4 border-t border-stroke flex justify-between items-center">
138
- <!-- Social icons -->
139
- <div class="flex items-center gap-3">
140
- <template v-for="(link, linkIndex) in member.links" :key="linkIndex">
141
- <a
142
- v-if="iconMap[link.icon]"
143
- :href="link.link"
144
- target="_blank"
145
- rel="noopener noreferrer"
146
- class="text-nickel hover:text-white transition-colors"
147
- >
148
- <img :src="iconMap[link.icon]" :alt="link.icon" class="size-5" />
149
- </a>
150
- </template>
151
- </div>
152
-
153
- <!-- Handle badge -->
154
- <div class="bg-beige rounded-md px-3 pb-0.5">
155
- <span class="text-nickel text-xs font-mono uppercase font-medium">{{ member.handle }}</span>
156
- </div>
157
- </div>
158
- </div>
159
- </div>
160
- </section>
161
- </template>
@@ -1,13 +0,0 @@
1
- <template>
2
- <div class="wrapper border-t flex flex-col justify-start items-center py-10 md:py-30">
3
- <div class="w-full sm:w-2xl flex flex-col justify-start items-center gap-5 px-5 sm:px-0">
4
- <h2 class="text-center text-primary text-balance">
5
- Meet the team
6
- </h2>
7
- <p class="self-stretch text-center text-balance text-grey">Including creators and core contributors to some of the
8
- most widely adopted open-source projects.</p>
9
- </div>
10
- </div>
11
- </template>
12
- <script setup lang="ts">
13
- </script>
@@ -1,223 +0,0 @@
1
- <script setup lang="ts">
2
- import {ref, computed, watch} from 'vue'
3
- import {type Post, formatDateShort} from './types'
4
- import {
5
- PaginationRoot,
6
- PaginationList,
7
- PaginationListItem,
8
- PaginationEllipsis,
9
- PaginationPrev,
10
- PaginationNext
11
- } from 'reka-ui'
12
-
13
- interface Props {
14
- posts: Post[]
15
- }
16
-
17
- const props = defineProps<Props>()
18
-
19
- const ITEMS_PER_PAGE = 10
20
-
21
- // State
22
- const currentPage = ref(1)
23
- const searchQuery = ref('')
24
- const selectedCategory = ref('all')
25
-
26
- // Extract unique categories from posts
27
- const allCategories = computed(() => {
28
- const categories = new Set<string>()
29
- props.posts.forEach(post => {
30
- if (post.category) {
31
- categories.add(post.category)
32
- }
33
- })
34
- return ['All', ...Array.from(categories).sort()]
35
- })
36
-
37
- // Filter posts by search query and category
38
- const filteredPosts = computed(() => {
39
- let result = props.posts
40
-
41
- // Filter by category
42
- if (selectedCategory.value !== 'all') {
43
- result = result.filter(post =>
44
- post.category.toLowerCase() === selectedCategory.value.toLowerCase()
45
- )
46
- }
47
-
48
- // Filter by search query
49
- if (searchQuery.value.trim()) {
50
- const query = searchQuery.value.toLowerCase().trim()
51
- result = result.filter(post =>
52
- post.title.toLowerCase().includes(query)
53
- )
54
- }
55
-
56
- return result
57
- })
58
-
59
- // Get paginated posts for current page
60
- const paginatedPosts = computed(() => {
61
- const start = (currentPage.value - 1) * ITEMS_PER_PAGE
62
- const end = start + ITEMS_PER_PAGE
63
- return filteredPosts.value.slice(start, end)
64
- })
65
-
66
- // Total items for pagination
67
- const totalItems = computed(() => filteredPosts.value.length)
68
-
69
- // Reset pagination when filters change
70
- watch([searchQuery, selectedCategory], () => {
71
- currentPage.value = 1
72
- })
73
-
74
- // Category selection handler
75
- function selectCategory(category: string, event: MouseEvent) {
76
- selectedCategory.value = category.toLowerCase() === 'all' ? 'all' : category
77
- // Blur button to prevent focus state from interfering with outline
78
- ;(event.target as HTMLButtonElement)?.blur()
79
- }
80
-
81
- // Check if category is active
82
- function isCategoryActive(category: string): boolean {
83
- if (category.toLowerCase() === 'all') {
84
- return selectedCategory.value === 'all'
85
- }
86
- return selectedCategory.value.toLowerCase() === category.toLowerCase()
87
- }
88
- </script>
89
-
90
- <template>
91
- <section class="wrapper wrapper--ticks border-t">
92
- <!-- Header Row -->
93
- <div class="px-6 md:px-20 pt-30 pb-10 flex flex-col gap-6">
94
- <!-- Title -->
95
- <h2 class="text-3xl md:text-4xl font-heading font-medium text-primary">All articles</h2>
96
-
97
- <!-- Filters Row -->
98
- <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 md:gap-6">
99
- <!-- Category Filters -->
100
- <div class="flex flex-wrap items-center gap-2 md:gap-3">
101
- <span class="text-grey text-xs font-medium font-mono uppercase tracking-wide mr-2">// Categories</span>
102
- <button
103
- v-for="category in allCategories"
104
- :key="category"
105
- type="button"
106
- :data-active="isCategoryActive(category)"
107
- class="px-3 py-1.5 rounded text-sm font-medium outline outline-1 transition-colors cursor-pointer bg-white text-primary outline-stroke data-[active=true]:outline-ruby data-[active=false]:hover:bg-beige focus:outline-1 capitalize"
108
- @click="selectCategory(category, $event)"
109
- >
110
- {{ category }}
111
- </button>
112
- </div>
113
-
114
- <!-- Search Input -->
115
- <div class="flex items-center gap-2 outline outline-1 outline-stroke rounded-lg px-3 py-2 w-full md:w-64">
116
- <svg class="size-5 text-grey shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
117
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
118
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
119
- </svg>
120
- <input
121
- v-model="searchQuery"
122
- type="text"
123
- placeholder="Search..."
124
- class="bg-transparent text-primary placeholder-grey text-sm outline-none w-full"
125
- />
126
- </div>
127
- </div>
128
- </div>
129
-
130
- <!-- Full-width border -->
131
- <div class="border-t border-stroke relative tick-left tick-right"></div>
132
-
133
- <!-- Articles List -->
134
- <div class="mx-6 md:mx-20 border-l border-r border-stroke flex flex-col gap-5" :class="{ 'pb-6 md:pb-20': totalItems <= ITEMS_PER_PAGE }">
135
- <a
136
- v-for="post in paginatedPosts"
137
- :key="post.url"
138
- :href="post.url"
139
- class="flex flex-col md:flex-row hover:bg-beige transition-colors border-t border-b border-stroke first:border-t-0 last:border-b-0"
140
- >
141
- <!-- Cover Image -->
142
- <div class="w-full md:w-[350px] bg-nickel overflow-hidden shrink-0">
143
- <img
144
- v-if="post.cover"
145
- :src="post.cover"
146
- :alt="post.title"
147
- class="w-full object-cover"
148
- />
149
- </div>
150
-
151
- <!-- Content -->
152
- <div class="flex flex-col md:flex-row md:items-center md:justify-between grow gap-4 md:gap-6 p-6 md:py-0 md:pr-10">
153
- <div class="flex flex-col gap-2">
154
- <span class="text-grey text-xs font-medium font-mono uppercase tracking-wide">// {{ post.category }}</span>
155
- <h4 class="text-xl text-primary font-normal leading-snug text-pretty">
156
- {{ post.title }}
157
- </h4>
158
- </div>
159
- <span class="text-grey text-xs font-medium font-mono uppercase tracking-wide whitespace-nowrap">
160
- {{ formatDateShort(post.date.string) }}
161
- </span>
162
- </div>
163
- </a>
164
-
165
- <!-- Empty State -->
166
- <div v-if="paginatedPosts.length === 0" class="p-10 text-center">
167
- <p class="text-grey text-base">No articles found matching your criteria.</p>
168
- </div>
169
- </div>
170
-
171
- <!-- Pagination -->
172
- <div v-if="totalItems > ITEMS_PER_PAGE" class="border-t border-stroke p-6 md:p-10 flex justify-center">
173
- <PaginationRoot
174
- v-model:page="currentPage"
175
- :total="totalItems"
176
- :items-per-page="ITEMS_PER_PAGE"
177
- :sibling-count="1"
178
- :show-edges="true"
179
- >
180
- <PaginationList v-slot="{ items }" class="flex items-center gap-1">
181
- <PaginationPrev class="size-10 flex items-center justify-center text-grey hover:text-primary transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed">
182
- <svg class="size-4" viewBox="0 0 24 24" fill="currentColor">
183
- <path
184
- d="M19,11H7.4l5.3-5.3c0.4-0.4,0.4-1,0-1.4s-1-0.4-1.4,0l-7,7c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.2-0.1,0.5,0,0.8c0.1,0.1,0.1,0.2,0.2,0.3l7,7c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3c0.4-0.4,0.4-1,0-1.4L7.4,13H19c0.6,0,1-0.4,1-1S19.6,11,19,11z"/>
185
- </svg>
186
- </PaginationPrev>
187
-
188
- <template v-for="(page, index) in items" :key="index">
189
- <PaginationListItem
190
- v-if="page.type === 'page'"
191
- :value="page.value"
192
- class="pagination-item size-10 flex items-center justify-center text-sm font-mono text-grey hover:text-primary transition-colors cursor-pointer"
193
- >
194
- {{ page.value }}
195
- </PaginationListItem>
196
- <PaginationEllipsis
197
- v-else
198
- :index="index"
199
- class="size-10 flex items-center justify-center text-sm font-mono text-grey"
200
- >
201
- &hellip;
202
- </PaginationEllipsis>
203
- </template>
204
-
205
- <PaginationNext class="size-10 flex items-center justify-center text-grey hover:text-primary transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed">
206
- <svg class="size-4" viewBox="0 0 24 24" fill="currentColor">
207
- <path
208
- d="M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"/>
209
- </svg>
210
- </PaginationNext>
211
- </PaginationList>
212
- </PaginationRoot>
213
- </div>
214
- </section>
215
- </template>
216
-
217
- <style scoped>
218
- /* Pagination active state - needs scoped style for data attribute selector */
219
- .pagination-item[data-selected="true"] {
220
- outline: 1px solid var(--color-stroke);
221
- color: var(--color-primary);
222
- }
223
- </style>