@voidzero-dev/vitepress-theme 3.1.0 → 3.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidzero-dev/vitepress-theme",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "description": "Shared VitePress theme for VoidZero projects",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -15,20 +15,12 @@
15
15
  "*.css"
16
16
  ],
17
17
  "peerDependencies": {
18
- "@docsearch/css": "^4.3.2",
19
- "@docsearch/js": "^4.3.2",
20
18
  "vitepress": "^2.0.0-alpha.12",
21
19
  "vue": "^3.5.0"
22
20
  },
23
- "peerDependenciesMeta": {
24
- "@docsearch/css": {
25
- "optional": true
26
- },
27
- "@docsearch/js": {
28
- "optional": true
29
- }
30
- },
31
21
  "dependencies": {
22
+ "@docsearch/css": "^4.3.2",
23
+ "@docsearch/js": "^4.3.2",
32
24
  "@rive-app/canvas": "^2.31.6",
33
25
  "@tailwindcss/typography": "^0.5.19",
34
26
  "@tailwindcss/vite": "^4.1.18",
@@ -1,44 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
-
4
- // Sponsor with optional explicit image path
5
- interface Sponsor {
6
- name: string
7
- url: string
8
- img?: string // If provided, use directly; otherwise auto-generate
9
- }
10
-
11
- interface SponsorWithImg extends Sponsor {
12
- img: string
13
- }
14
-
15
- // Tier configuration structure
16
- interface TierConfig {
17
- displayName: string
18
- columns: number
19
- size?: 'large' | 'medium' | 'small' | 'avatar'
20
- avatarMode?: boolean // For backers dense grid
21
- }
22
-
23
- // Processed tier for rendering
24
- interface ProcessedTier {
25
- tierKey: string
26
- displayName: string
27
- columns: number
28
- size: 'large' | 'medium' | 'small' | 'avatar'
29
- avatarMode: boolean
30
- items: SponsorWithImg[]
31
- }
3
+ import type { SponsorTier, SponsorSize } from '../../types/sponsors'
32
4
 
33
5
  interface Props {
34
- sponsors?: Record<string, Sponsor[]>
35
- tierConfig?: Record<string, Partial<TierConfig>> // Override defaults
36
- imageBasePath?: string // Default: '/sponsors/'
37
- sideBySideTiers?: [string, string] // e.g., ['bronze', 'backers'] for OXC layout
6
+ sponsors?: SponsorTier[]
38
7
  heading?: string
39
8
  description?: string
40
9
  sponsorLink?: string
41
10
  sponsorLinkText?: string
11
+ sideBySideTiers?: [string, string] // e.g., ['bronze', 'backers'] for side-by-side layout
12
+ logoStyle?: 'opencollective'
42
13
  }
43
14
 
44
15
  const props = withDefaults(defineProps<Props>(), {
@@ -48,285 +19,176 @@ const props = withDefaults(defineProps<Props>(), {
48
19
  sponsorLinkText: 'Become a Sponsor',
49
20
  })
50
21
 
51
- // Default tier configuration with display names, column counts, and sizes
52
- const defaultTierConfig: Record<string, TierConfig> = {
53
- // Vite tiers
54
- partnership: { displayName: 'IN PARTNERSHIP WITH', columns: 2, size: 'large' },
55
- platinum: { displayName: 'PLATINUM SPONSORS', columns: 3, size: 'large' },
56
-
57
- // Vitest tiers
58
- special: { displayName: 'SPECIAL SPONSORS', columns: 3, size: 'large' },
59
-
60
- // Shared tier
61
- gold: { displayName: 'GOLD SPONSORS', columns: 5, size: 'medium' },
62
-
63
- // OXC tiers
64
- bronze: { displayName: 'BRONZE SPONSORS', columns: 1, size: 'medium' },
65
- silver: { displayName: 'SILVER SPONSORS', columns: 3, size: 'medium' },
66
- backers: { displayName: 'BACKERS', columns: 15, size: 'avatar', avatarMode: true },
22
+ const sizeConfig: Record<SponsorSize, { columns: number; padding: string; logoSize: string; avatarMode?: boolean }> = {
23
+ big: { columns: 3, padding: 'py-16', logoSize: 'h-13 w-[220px]' },
24
+ medium: { columns: 5, padding: 'py-7', logoSize: 'h-8 w-[120px]' },
25
+ small: { columns: 7, padding: 'py-5', logoSize: 'h-6 w-[100px]' },
26
+ avatar: { columns: 15, padding: 'py-3', logoSize: 'w-10 h-10', avatarMode: true },
67
27
  }
68
28
 
69
- // Resolve image path - use explicit img or auto-generate from name
70
- const resolveImagePath = (sponsor: Sponsor): string => {
71
- if (sponsor.img) return sponsor.img
72
- const basePath = props.imageBasePath ?? '/sponsors/'
73
- return `${basePath}${sponsor.name.toLowerCase().replace(/\s+/g, '')}.svg`
29
+ const gridClasses: Record<number, string> = {
30
+ 1: 'grid-cols-1',
31
+ 2: 'grid-cols-1 sm:grid-cols-2',
32
+ 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
33
+ 4: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-4',
34
+ 5: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5',
35
+ 6: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',
36
+ 7: 'grid-cols-2 sm:grid-cols-4 lg:grid-cols-7',
74
37
  }
75
38
 
76
- // Get merged tier config (defaults + overrides)
77
- const getMergedTierConfig = (tierKey: string): TierConfig => {
78
- const defaultConfig = defaultTierConfig[tierKey] || {
79
- displayName: tierKey.toUpperCase(),
80
- columns: 5,
81
- size: 'medium' as const,
82
- avatarMode: false,
83
- }
84
- const overrideConfig = props.tierConfig?.[tierKey] || {}
85
- return { ...defaultConfig, ...overrideConfig }
39
+ const getEffectiveColumns = (size: SponsorSize, itemCount: number) =>
40
+ Math.min(sizeConfig[size].columns, itemCount)
41
+
42
+ const getGridColumns = (size: SponsorSize, itemCount: number) =>
43
+ gridClasses[getEffectiveColumns(size, itemCount)] || 'grid-cols-1'
44
+
45
+ const getEmptyCells = (itemCount: number, size: SponsorSize) => {
46
+ const cols = getEffectiveColumns(size, itemCount)
47
+ const remainder = itemCount % cols
48
+ return remainder === 0 ? 0 : cols - remainder
86
49
  }
87
50
 
88
- // Process a single tier
89
- const processTier = (tierKey: string, items: Sponsor[]): ProcessedTier => {
90
- const config = getMergedTierConfig(tierKey)
91
- return {
92
- tierKey,
93
- displayName: config.displayName,
94
- columns: config.columns,
95
- size: config.size || 'medium',
96
- avatarMode: config.avatarMode || false,
97
- items: items.map(sponsor => ({
98
- ...sponsor,
99
- img: resolveImagePath(sponsor),
100
- })),
101
- }
51
+ // Calculate empty slots for backers grid (maintain visual consistency)
52
+ const getBackerEmptySlots = (itemCount: number, minSlots: number = 30) => {
53
+ const targetSlots = Math.max(itemCount, minSlots)
54
+ return Math.max(0, targetSlots - itemCount)
102
55
  }
103
56
 
104
- // Side-by-side tier data (for OXC layout)
57
+ // Side-by-side tier data (for layouts like OXC: Bronze + Backers)
105
58
  const sideBySideData = computed(() => {
106
59
  if (!props.sponsors || !props.sideBySideTiers) return null
107
60
 
108
- const [leftTierKey, rightTierKey] = props.sideBySideTiers
109
- const leftItems = props.sponsors[leftTierKey]
110
- const rightItems = props.sponsors[rightTierKey]
61
+ const [leftTierName, rightTierName] = props.sideBySideTiers
62
+ const leftTier = props.sponsors.find(t => t.tier.toLowerCase() === leftTierName.toLowerCase())
63
+ const rightTier = props.sponsors.find(t => t.tier.toLowerCase() === rightTierName.toLowerCase())
111
64
 
112
- if (!leftItems || !rightItems) return null
65
+ if (!leftTier || !rightTier) return null
113
66
 
114
- return {
115
- left: processTier(leftTierKey, leftItems),
116
- right: processTier(rightTierKey, rightItems),
117
- }
67
+ return { left: leftTier, right: rightTier }
118
68
  })
119
69
 
120
70
  // Standard stacked tier data (excludes side-by-side tiers)
121
- const stackedSponsorData = computed(() => {
71
+ const stackedSponsors = computed(() => {
122
72
  if (!props.sponsors) return []
73
+ if (!props.sideBySideTiers) return props.sponsors
123
74
 
124
- const sideBySideKeys = props.sideBySideTiers || []
125
-
126
- return Object.entries(props.sponsors)
127
- .filter(([tierKey]) => !sideBySideKeys.includes(tierKey))
128
- .map(([tierKey, items]) => processTier(tierKey, items))
75
+ const sideBySideNames = props.sideBySideTiers.map(n => n.toLowerCase())
76
+ return props.sponsors.filter(tier => !sideBySideNames.includes(tier.tier.toLowerCase()))
129
77
  })
130
-
131
- // Get grid columns class based on specified columns
132
- const getGridColumns = (columns: number) => {
133
- const gridClasses: Record<number, string> = {
134
- 1: 'grid-cols-1',
135
- 2: 'grid-cols-1 sm:grid-cols-2',
136
- 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
137
- 4: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-4',
138
- 5: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5',
139
- 6: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',
140
- 7: 'grid-cols-2 sm:grid-cols-4 lg:grid-cols-7',
141
- }
142
- return gridClasses[columns] || 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4'
143
- }
144
-
145
- // Size-based padding classes
146
- const getSponsorPadding = (size: string) => {
147
- const paddingMap: Record<string, string> = {
148
- large: 'py-16',
149
- medium: 'py-7',
150
- small: 'py-5',
151
- avatar: 'py-3',
152
- }
153
- return paddingMap[size] || 'py-7'
154
- }
155
-
156
- // Size-based logo dimension classes
157
- const getLogoSize = (size: string) => {
158
- const sizeMap: Record<string, string> = {
159
- large: 'max-h-12 max-w-[200px]',
160
- medium: 'max-h-6 max-w-[120px]',
161
- small: 'max-h-5 max-w-[100px]',
162
- avatar: 'max-h-8 max-w-[80px]',
163
- }
164
- return sizeMap[size] || 'max-h-6 max-w-[120px]'
165
- }
166
-
167
- // Calculate number of empty cells needed to fill the last row
168
- const getEmptyCells = (itemCount: number, columns: number) => {
169
- const remainder = itemCount % columns
170
- return remainder === 0 ? 0 : columns - remainder
171
- }
172
-
173
- // Calculate empty slots for backers grid (maintain visual consistency)
174
- const getBackerEmptySlots = (itemCount: number, minSlots: number = 30) => {
175
- const targetSlots = Math.max(itemCount, minSlots)
176
- return Math.max(0, targetSlots - itemCount)
177
- }
178
78
  </script>
179
79
 
180
80
  <template>
181
- <div class="wrapper wrapper--ticks border-t py-14 sm:py-30 px-5 sm:px-10">
182
- <div class="flex flex-col md:flex-row justify-between items-center gap-8 md:gap-20 text-center md:text-left md:pl-15">
81
+ <div class="wrapper wrapper--ticks border-t py-14 sm:py-30 px-10">
82
+ <div
83
+ class="flex flex-col md:flex-row justify-between items-center gap-8 md:gap-20 text-center md:text-left md:pl-15">
183
84
  <div class="flex flex-col gap-3">
184
85
  <h3 class="text-white max-w-xl text-balance">{{ heading }}</h3>
185
86
  <p class="max-w-lg text-white/70 text-balance">{{ description }}</p>
186
87
  <a :href="sponsorLink" target="_blank" class="button w-fit mt-8 mx-auto md:mx-0">{{ sponsorLinkText }}</a>
187
88
  </div>
188
- <div class="flex gap-8 md:gap-12 items-start justify-center md:justify-start md:pr-25">
189
- <img src="../../assets/vite/vite-by-voidzero.png" alt="Brought to you by VoidZero" class="h-44 max-w-full object-contain mt-10 md:mt-0"/>
190
- </div>
89
+ <a class="flex gap-8 md:gap-12 items-start justify-center md:justify-start md:pr-25" href="https://voidzero.dev"
90
+ target="_blank">
91
+ <img src="../../assets/vite/vite-by-voidzero.png" alt="Brought to you by VoidZero"
92
+ class="h-44 max-w-full object-contain mt-10 md:mt-0" />
93
+ </a>
191
94
  </div>
192
95
  </div>
193
- <div v-if="sponsors && Object.keys(sponsors).length > 0" class="wrapper">
194
- <!-- Side-by-Side Layout (OXC: Bronze + Backers) -->
96
+ <div v-if="sponsors && sponsors.length > 0" class="wrapper"
97
+ :class="{ 'sponsor-style-oc': logoStyle === 'opencollective' }">
98
+ <!-- Standard Stacked Sponsors Grid -->
99
+ <div v-for="(tier, tierIndex) in stackedSponsors" :key="tierIndex" class="border-t border-nickel">
100
+ <!-- Tier Heading -->
101
+ <div class="py-5 px-5 sm:px-10 border-b border-nickel">
102
+ <span class="text-white/70 text-xs font-mono uppercase tracking-wide">
103
+ {{ tier.tier }}
104
+ </span>
105
+ </div>
106
+
107
+ <!-- Avatar Mode Grid (for backers-style tiers) -->
108
+ <div v-if="sizeConfig[tier.size].avatarMode" class="backers-grid px-5 sm:px-10 py-6">
109
+ <a v-for="(sponsor, index) in tier.items" :key="index" :href="sponsor.url" target="_blank"
110
+ rel="noopener noreferrer" class="backer-avatar group relative">
111
+ <img :src="sponsor.img" :alt="sponsor.name"
112
+ class="w-10 h-10 rounded object-cover border border-nickel transition-all group-hover:border-white/50 group-hover:scale-110" />
113
+ <span class="backer-tooltip">{{ sponsor.name }}</span>
114
+ </a>
115
+ <!-- Empty placeholder slots -->
116
+ <div v-for="emptyIndex in getBackerEmptySlots(tier.items.length)" :key="`empty-${emptyIndex}`"
117
+ class="backer-avatar-placeholder" aria-hidden="true">
118
+ <div class="w-10 h-10 rounded border border-nickel/30"></div>
119
+ </div>
120
+ </div>
121
+
122
+ <!-- Standard Logo Grid -->
123
+ <div v-else class="grid [&>*]:border-r [&>*]:border-b [&>*]:border-nickel"
124
+ :class="getGridColumns(tier.size, tier.items.length)">
125
+ <a v-for="(sponsor, index) in tier.items" :key="index" :href="sponsor.url" target="_blank"
126
+ rel="noopener noreferrer"
127
+ class="sponsor-link flex items-center justify-center px-8 transition-opacity opacity-85 hover:opacity-100"
128
+ :class="sizeConfig[tier.size].padding">
129
+ <img :src="sponsor.img" :alt="sponsor.name" class="sponsor-logo object-contain"
130
+ :class="sizeConfig[tier.size].logoSize" />
131
+ <span class="sponsor-name ml-5">{{ sponsor.name }}</span>
132
+ </a>
133
+
134
+ <!-- Empty cells to fill incomplete rows -->
135
+ <div v-for="emptyIndex in getEmptyCells(tier.items.length, tier.size)" :key="`empty-${emptyIndex}`"
136
+ class="sponsor-link flex items-center justify-center px-8" :class="sizeConfig[tier.size].padding"
137
+ aria-hidden="true" />
138
+ </div>
139
+ </div>
140
+
141
+ <!-- Side-by-Side Layout (e.g., Bronze + Backers) -->
195
142
  <div v-if="sideBySideData" class="grid grid-cols-1 lg:grid-cols-2 border-t border-nickel">
196
- <!-- Left Tier (Bronze Sponsors) -->
143
+ <!-- Left Tier -->
197
144
  <div class="border-b lg:border-b-0 lg:border-r border-nickel">
198
145
  <div class="py-5 px-5 sm:px-10 border-b border-nickel">
199
146
  <span class="text-white/70 text-xs font-mono uppercase tracking-wide">
200
- {{ sideBySideData.left.displayName }}
147
+ {{ sideBySideData.left.tier }}
201
148
  </span>
202
149
  </div>
203
150
  <div class="flex flex-col">
204
- <a
205
- v-for="(sponsor, index) in sideBySideData.left.items"
206
- :key="index"
207
- :href="sponsor.url"
208
- target="_blank"
151
+ <a v-for="(sponsor, index) in sideBySideData.left.items" :key="index" :href="sponsor.url" target="_blank"
209
152
  rel="noopener noreferrer"
210
- class="sponsor-link flex items-center justify-center px-8 py-12 transition-opacity opacity-70 hover:opacity-100 border-b border-nickel last:border-b-0"
211
- >
212
- <img
213
- :src="sponsor.img"
214
- :alt="sponsor.name"
215
- class="sponsor-logo object-contain max-h-8 max-w-[160px]"
216
- />
153
+ class="sponsor-link flex items-center justify-center px-8 py-12 transition-opacity opacity-85 hover:opacity-100 border-b border-nickel last:border-b-0">
154
+ <img :src="sponsor.img" :alt="sponsor.name" class="sponsor-logo object-contain"
155
+ :class="sizeConfig[sideBySideData.left.size].logoSize" />
156
+ <span class="sponsor-name ml-5">{{ sponsor.name }}</span>
217
157
  </a>
218
158
  </div>
219
159
  </div>
220
160
 
221
- <!-- Right Tier (Backers) -->
161
+ <!-- Right Tier (Backers style) -->
222
162
  <div>
223
163
  <div class="py-5 px-5 sm:px-10 border-b border-nickel">
224
164
  <span class="text-white/70 text-xs font-mono uppercase tracking-wide">
225
- {{ sideBySideData.right.displayName }}
165
+ {{ sideBySideData.right.tier }}
226
166
  </span>
227
167
  </div>
228
- <div class="backers-grid px-5 sm:px-10 py-6">
229
- <a
230
- v-for="(sponsor, index) in sideBySideData.right.items"
231
- :key="index"
232
- :href="sponsor.url"
233
- target="_blank"
234
- rel="noopener noreferrer"
235
- class="backer-avatar group relative"
236
- >
237
- <img
238
- :src="sponsor.img"
239
- :alt="sponsor.name"
240
- class="w-10 h-10 rounded object-cover border border-nickel transition-all group-hover:border-white/50 group-hover:scale-110"
241
- />
168
+ <div v-if="sizeConfig[sideBySideData.right.size].avatarMode" class="backers-grid px-5 sm:px-10 py-6">
169
+ <a v-for="(sponsor, index) in sideBySideData.right.items" :key="index" :href="sponsor.url" target="_blank"
170
+ rel="noopener noreferrer" class="backer-avatar group relative">
171
+ <img :src="sponsor.img" :alt="sponsor.name"
172
+ class="w-10 h-10 rounded object-cover border border-nickel transition-all group-hover:border-white/50 group-hover:scale-110" />
242
173
  <span class="backer-tooltip">{{ sponsor.name }}</span>
243
174
  </a>
244
175
  <!-- Empty placeholder slots for visual consistency -->
245
- <div
246
- v-for="emptyIndex in getBackerEmptySlots(sideBySideData.right.items.length)"
247
- :key="`empty-${emptyIndex}`"
248
- class="backer-avatar-placeholder"
249
- aria-hidden="true"
250
- >
251
- <div class="w-10 h-10 rounded border border-nickel/30"></div>
252
- </div>
253
- </div>
254
- </div>
255
- </div>
256
-
257
- <!-- Standard Stacked Sponsors Grid -->
258
- <div v-if="stackedSponsorData && stackedSponsorData.length > 0">
259
- <div
260
- v-for="(tierData, tierIndex) in stackedSponsorData"
261
- :key="tierIndex"
262
- class="border-t border-nickel"
263
- >
264
- <!-- Tier Heading -->
265
- <div class="py-5 px-5 sm:px-10 border-b border-nickel">
266
- <span class="text-white/70 text-xs font-mono uppercase tracking-wide">
267
- {{ tierData.displayName }}
268
- </span>
269
- </div>
270
-
271
- <!-- Avatar Mode Grid (for backers-style tiers) -->
272
- <div v-if="tierData.avatarMode" class="backers-grid px-5 sm:px-10 py-6">
273
- <a
274
- v-for="(sponsor, index) in tierData.items"
275
- :key="index"
276
- :href="sponsor.url"
277
- target="_blank"
278
- rel="noopener noreferrer"
279
- class="backer-avatar group relative"
280
- >
281
- <img
282
- :src="sponsor.img"
283
- :alt="sponsor.name"
284
- class="w-10 h-10 rounded object-cover border border-nickel transition-all group-hover:border-white/50 group-hover:scale-110"
285
- />
286
- <span class="backer-tooltip">{{ sponsor.name }}</span>
287
- </a>
288
- <!-- Empty placeholder slots -->
289
- <div
290
- v-for="emptyIndex in getBackerEmptySlots(tierData.items.length)"
291
- :key="`empty-${emptyIndex}`"
292
- class="backer-avatar-placeholder"
293
- aria-hidden="true"
294
- >
176
+ <div v-for="emptyIndex in getBackerEmptySlots(sideBySideData.right.items.length)" :key="`empty-${emptyIndex}`"
177
+ class="backer-avatar-placeholder" aria-hidden="true">
295
178
  <div class="w-10 h-10 rounded border border-nickel/30"></div>
296
179
  </div>
297
180
  </div>
298
-
299
- <!-- Standard Logo Grid -->
300
- <div
301
- v-else
302
- class="grid [&>*]:border-r [&>*]:border-b [&>*]:border-nickel"
303
- :class="getGridColumns(tierData.columns)"
304
- >
305
- <a
306
- v-for="(sponsor, index) in tierData.items"
307
- :key="index"
308
- :href="sponsor.url"
309
- target="_blank"
181
+ <!-- Fallback to standard grid if not avatar mode -->
182
+ <div v-else class="grid [&>*]:border-r [&>*]:border-b [&>*]:border-nickel"
183
+ :class="getGridColumns(sideBySideData.right.size, sideBySideData.right.items.length)">
184
+ <a v-for="(sponsor, index) in sideBySideData.right.items" :key="index" :href="sponsor.url" target="_blank"
310
185
  rel="noopener noreferrer"
311
- class="sponsor-link flex items-center justify-center px-8 transition-opacity opacity-70 hover:opacity-100"
312
- :class="getSponsorPadding(tierData.size)"
313
- >
314
- <img
315
- :src="sponsor.img"
316
- :alt="sponsor.name"
317
- class="sponsor-logo object-contain"
318
- :class="getLogoSize(tierData.size)"
319
- />
186
+ class="sponsor-link flex items-center justify-center px-8 transition-opacity opacity-85 hover:opacity-100"
187
+ :class="sizeConfig[sideBySideData.right.size].padding">
188
+ <img :src="sponsor.img" :alt="sponsor.name" class="sponsor-logo object-contain"
189
+ :class="sizeConfig[sideBySideData.right.size].logoSize" />
190
+ <span class="sponsor-name ml-5">{{ sponsor.name }}</span>
320
191
  </a>
321
-
322
- <!-- Empty cells to fill incomplete rows -->
323
- <div
324
- v-for="emptyIndex in getEmptyCells(tierData.items.length, tierData.columns)"
325
- :key="`empty-${emptyIndex}`"
326
- class="sponsor-link flex items-center justify-center px-8"
327
- :class="getSponsorPadding(tierData.size)"
328
- aria-hidden="true"
329
- />
330
192
  </div>
331
193
  </div>
332
194
  </div>
@@ -334,6 +196,31 @@ const getBackerEmptySlots = (itemCount: number, minSlots: number = 30) => {
334
196
  </template>
335
197
 
336
198
  <style scoped>
199
+ /* Sponsor grid - clip outer borders using overflow */
200
+ .grid[class*="grid-cols"] {
201
+ overflow: hidden;
202
+ margin-right: -1px;
203
+ margin-bottom: -1px;
204
+ }
205
+
206
+ .sponsor-logo {
207
+ filter: grayscale(1) invert(1);
208
+ }
209
+
210
+ .sponsor-style-oc .sponsor-logo {
211
+ filter: none;
212
+ width: auto;
213
+ border-radius: 0.25rem;
214
+ }
215
+
216
+ .sponsor-name {
217
+ display: none;
218
+ }
219
+
220
+ .sponsor-style-oc .sponsor-name {
221
+ display: inline-block;
222
+ }
223
+
337
224
  /* Backers dense avatar grid */
338
225
  .backers-grid {
339
226
  display: flex;
@@ -389,11 +276,4 @@ const getBackerEmptySlots = (itemCount: number, minSlots: number = 30) => {
389
276
  .backer-avatar-placeholder div {
390
277
  background: transparent;
391
278
  }
392
-
393
- /* Sponsor grid - clip outer borders using overflow */
394
- .grid[class*="grid-cols"] {
395
- overflow: hidden;
396
- margin-right: -1px;
397
- margin-bottom: -1px;
398
- }
399
279
  </style>
@@ -1,40 +1,14 @@
1
1
  <script setup lang="ts">
2
- import { computed } from 'vue'
3
2
  import LogoGrid from '@components/shared/LogoGrid.vue'
4
3
 
5
- // Glob import all client logos
6
- const logoModules = import.meta.glob('../../assets/clients/*.svg', {
7
- eager: true,
8
- import: 'default'
9
- })
4
+ const props = defineProps<{
5
+ logos: string[]
6
+ }>()
10
7
 
11
- // Create logo asset map: { 'openai': 'url', 'framer': 'url', ... }
12
- const logoAssets: Record<string, string> = {}
13
- Object.entries(logoModules).forEach(([path, module]) => {
14
- const filename = path.split('/').pop()?.replace('.svg', '') || ''
15
- logoAssets[filename] = module as string
16
- })
17
-
18
- // Default logo order (matches current implementation)
19
- const DEFAULT_LOGO_ORDER = ['openai', 'framer', 'linear', 'ramp', 'shopify']
20
-
21
- // Props interface
22
- interface Props {
23
- logos?: string[] // Optional: array of logo names in desired order
24
- }
25
-
26
- const props = defineProps<Props>()
27
-
28
- // Computed logos array - resolves logo names to actual assets
29
- const logos = computed(() => {
30
- const order = props.logos || DEFAULT_LOGO_ORDER
31
- return order
32
- .filter(name => name in logoAssets) // Filter out invalid names
33
- .map(name => ({
34
- src: logoAssets[name],
35
- alt: name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
36
- }))
37
- })
8
+ const logos = props.logos.map(name => ({
9
+ src: `/trusted-by/${name}.svg`,
10
+ alt: name,
11
+ }))
38
12
  </script>
39
13
 
40
14
  <template>
@@ -51,5 +25,6 @@ const logos = computed(() => {
51
25
  <style scoped>
52
26
  :deep(img) {
53
27
  filter: brightness(0) invert(1);
28
+ opacity: 0.7;
54
29
  }
55
30
  </style>
@@ -11,11 +11,11 @@
11
11
  <p class="text-white/70 text-base max-w-[25rem] text-pretty">Node.js-compatible CJS and ESM module resolution
12
12
  </p>
13
13
  <ul class="checkmark-list">
14
- <li>Behavior alignment with <code
15
- class="mx-1 outline-none bg-nickel/50 text-aqua">enhanced-resolve</code></li>
14
+ <li>Behavior alignment with <code class="mx-1 outline-none bg-nickel/50 text-aqua">enhanced-resolve</code>
15
+ </li>
16
16
  <li>28x faster than <code class="mx-1 outline-none bg-nickel/50 text-aqua">enhanced-resolve</code>
17
- <li>Highly customizable</li>
18
17
  </li>
18
+ <li>Highly customizable</li>
19
19
  </ul>
20
20
  <a href="/docs/guide/usage/resolver" class="button w-fit mt-6">Usage Guide</a>
21
21
  </div>
@@ -7,17 +7,19 @@ import oxcAnimation from '@assets/oxc/animations/640_x_630_oxc masthead_.riv'
7
7
  <div class="wrapper wrapper--ticks grid md:grid-cols-2 w-full border-nickel divide-x">
8
8
  <div class="flex flex-col p-10 justify-center items-center md:items-start">
9
9
  <div class="flex flex-col gap-5 max-w-[30rem] text-center md:text-left items-center md:items-start">
10
- <div class="flex items-center gap-2">
10
+ <a class="flex items-center gap-2" href="https://voidzero.dev" target="_blank">
11
11
  <span class="text-grey text-xs font-mono uppercase tracking-wide">By</span>
12
12
  <img src="@assets/logos/voidzero-light.svg" alt="VoidZero" class="h-2.5">
13
- </div>
13
+ </a>
14
14
  <h1 class="text-white text-pretty">The JavaScript Oxidation Compiler</h1>
15
- <p class="text-white/70 text-lg max-w-[25rem] text-pretty">A collection of high-performance JavaScript tools written in Rust</p>
15
+ <p class="text-white/70 text-lg max-w-[25rem] text-pretty">A collection of high-performance JavaScript tools
16
+ written in Rust</p>
16
17
  <div class="flex items-center gap-5 mt-8">
17
18
  <a href="/docs/guide/introduction" class="button button--primary inline-block w-fit">
18
19
  Get Started
19
20
  </a>
20
- <a href="https://github.com/oxc-project/oxc" target="_blank" rel="noopener noreferrer" class="button inline-block w-fit">
21
+ <a href="https://github.com/oxc-project/oxc" target="_blank" rel="noopener noreferrer"
22
+ class="button inline-block w-fit">
21
23
  View on GitHub
22
24
  </a>
23
25
  </div>
@@ -25,20 +27,11 @@ import oxcAnimation from '@assets/oxc/animations/640_x_630_oxc masthead_.riv'
25
27
  </div>
26
28
  <div class="flex flex-col min-h-[22rem] sm:min-h-[30rem]">
27
29
  <div class="relative px-10 h-full flex flex-col justify-center overflow-clip pb-10 sm:pb-0">
28
- <RiveAnimation
29
- :desktop-src="oxcAnimation"
30
- :mobile-src="oxcAnimation"
31
- :desktop-width="640"
32
- :desktop-height="630"
33
- :mobile-width="640"
34
- :mobile-height="630"
35
- canvas-class="w-full"
36
- />
30
+ <RiveAnimation :desktop-src="oxcAnimation" :mobile-src="oxcAnimation" :desktop-width="640" :desktop-height="630"
31
+ :mobile-width="640" :mobile-height="630" canvas-class="w-full" />
37
32
  </div>
38
33
  </div>
39
34
  </div>
40
35
  </template>
41
36
 
42
- <style scoped>
43
-
44
- </style>
37
+ <style scoped></style>
@@ -2,10 +2,10 @@
2
2
  <div class="wrapper wrapper--ticks grid md:grid-cols-2 w-full border-nickel divide-x">
3
3
  <div class="flex flex-col p-10 justify-between items-center md:items-start">
4
4
  <div class="flex flex-col gap-5 text-center md:text-left items-center md:items-start">
5
- <div class="flex items-center gap-2">
5
+ <a class="flex items-center gap-2" href="https://voidzero.dev" target="_blank">
6
6
  <span class="text-grey text-xs font-mono uppercase tracking-wide">By</span>
7
7
  <img src="@assets/logos/voidzero-light.svg" alt="VoidZero" class="h-2.5">
8
- </div>
8
+ </a>
9
9
  <h1 class="text-white text-pretty max-w-[35rem]">Blazing Fast<br>Rust-based bundler for JavaScript</h1>
10
10
  <p class="text-white/70 text-lg max-w-[30rem] text-pretty">with Rollup-compatible API and esbuild feature parity</p>
11
11
  <div class="flex items-center gap-5 mt-6">
@@ -122,7 +122,7 @@ const testimonials: Testimonial[] = [
122
122
  >
123
123
  <!-- Comment -->
124
124
  <div class="relative z-[2] flex flex-col gap-4">
125
- <p v-for="(paragraph, pIndex) in testimonial.comment" :key="pIndex" class="text-white/70 leading-relaxed">
125
+ <p v-for="(paragraph, pIndex) in testimonial.comment" :key="pIndex" class="text-white/70 leading-relaxed text-sm sm:text-base">
126
126
  {{ paragraph }}
127
127
  </p>
128
128
  </div>
@@ -8,7 +8,8 @@ import viteAnimation from '@assets/vite/animations/563_x_420_rich_features.riv'
8
8
  <div class="flex flex-col gap-3 justify-between">
9
9
  <div class="p-5 sm:p-10 flex flex-col gap-3">
10
10
  <h5 class="text-balance sm:text-pretty text-white">Instant Server Start</h5>
11
- <p class="sm:max-w-[28rem] text-pretty">On demand source file serving over native ESM, with blazing fast
11
+ <p class="sm:max-w-[28rem] text-pretty">On demand source file serving over
12
+ native ESM, with blazing fast
12
13
  dependency pre-bundling.</p>
13
14
  </div>
14
15
  <div class="card-bg1 p-10 sm:p-15 flex justify-center">
@@ -19,18 +20,19 @@ import viteAnimation from '@assets/vite/animations/563_x_420_rich_features.riv'
19
20
  <div class="flex flex-col gap-3 justify-between border-r-0">
20
21
  <div class="p-5 sm:p-10 flex flex-col gap-3">
21
22
  <h5 class="text-white">Lightning Fast HMR</h5>
22
- <p class="max-w-[26rem] text-pretty">Hot Module Replacement (HMR) that delivers updates instantly as you save,
23
- and stays fast as your app grows.</p>
23
+ <p class="max-w-[26rem] text-pretty">Instantly reflect changes as you save, no matter how
24
+ big your app is.</p>
24
25
  </div>
25
- <div class="relative min-h-100 -mt-15">
26
- <img src="@assets/vite/vite-featurepanel-2-terminal.png" inert loading="lazy" alt="lightning fast hot module replacement"
27
- class="absolute bottom-0 right-0 w-full max-w-[560px]">
26
+ <div class="flex justify-end">
27
+ <img src="@assets/vite/vite-featurepanel-2-terminal.png" class="md:max-w-[80%]" inert loading="lazy"
28
+ alt="lightning fast hot module replacement">
28
29
  </div>
29
30
  </div>
30
31
 
31
- <div class="p-5 sm:p-10 pb-0 sm:pb-0 flex flex-col gap-3 border-b-0">
32
+ <div class="p-5 sm:p-10 pb-0 sm:pb-0 flex flex-col gap-3 lg:border-b-0">
32
33
  <h5 class="text-white">Rich Features Out of the Box</h5>
33
- <p class="sm:max-w-[28rem] text-pretty">TypeScript, JSX, CSS, Workers, Web Assembly... and more with just a plugin away.</p>
34
+ <p class="sm:max-w-[28rem] text-pretty">TypeScript, JSX, CSS, Workers, Web
35
+ Assembly... and more with just a plugin away.</p>
34
36
  <RiveAnimation :desktop-src="viteAnimation" :mobile-src="viteAnimation" :desktop-width="563" :desktop-height="300"
35
37
  :mobile-width="563" :mobile-height="420" canvas-class="w-full" />
36
38
  </div>
@@ -38,7 +40,8 @@ import viteAnimation from '@assets/vite/animations/563_x_420_rich_features.riv'
38
40
  <div class="flex flex-col gap-3 justify-between border-r-0 border-b-0">
39
41
  <div class="p-5 sm:p-10 flex flex-col gap-3">
40
42
  <h5 class="text-white">Optimized Build</h5>
41
- <p class="max-w-[25rem] text-pretty">Advanced tree-shaking, built-in minification, fine-grained chunking control
43
+ <p class="max-w-[25rem] text-pretty">Advanced tree-shaking, built-in
44
+ minification, fine-grained chunking control
42
45
  powered by Rolldown.</p>
43
46
  </div>
44
47
  <div class="card-bg p-10 sm:p-15 flex justify-center">
@@ -7,7 +7,8 @@ import flexiblePluginAnimation from '@assets/vite/animations/640_x_300_flexible_
7
7
  <section class="wrapper wrapper--ticks border-t grid lg:grid-cols-2 divide-x divide-y divide-nickel">
8
8
  <div class="p-5 sm:p-10 flex flex-col gap-3">
9
9
  <h5 class="text-balance sm:text-pretty text-white">Flexible Plugin System</h5>
10
- <p class="sm:max-w-[28rem] text-pretty">Vite plugins extends Rollup's well-designed plugin interface with a few
10
+ <p class="sm:max-w-[28rem] text-pretty">Vite plugins extends Rollup's
11
+ well-designed plugin interface with a few
11
12
  extra Vite-specific options.</p>
12
13
  <RiveAnimation :desktop-src="flexiblePluginAnimation" :desktop-width="640" :desktop-height="300"
13
14
  canvas-class="w-[calc(100%_+_2.5rem)] mt-5 -mx-5" />
@@ -23,9 +24,10 @@ import flexiblePluginAnimation from '@assets/vite/animations/640_x_300_flexible_
23
24
  </div>
24
25
  </div>
25
26
 
26
- <div class="p-5 sm:p-10 flex flex-col gap-3 border-b-0">
27
+ <div class="p-5 sm:p-10 flex flex-col gap-3 lg:border-b-0">
27
28
  <h5 class="text-white">First class SSR Support</h5>
28
- <p class="sm:max-w-[28rem] text-pretty mb-12 sm:mb-16">It's never been easier to setup custom SSR (Server-Side
29
+ <p class="sm:max-w-[28rem] text-pretty mb-12 sm:mb-16">It's never been easier to
30
+ setup custom SSR (Server-Side
29
31
  Rendering), or build your own SSR framework.</p>
30
32
  <img src="@assets/vite/vite-ssr-support.png" alt="SSR Support" loading="lazy" class="w-full px-5">
31
33
  </div>
@@ -33,7 +35,8 @@ import flexiblePluginAnimation from '@assets/vite/animations/640_x_300_flexible_
33
35
  <div class="flex flex-col gap-3 justify-between">
34
36
  <div class="p-5 sm:p-10 flex flex-col gap-3">
35
37
  <h5 class="text-white">Continuous ecosystem integration</h5>
36
- <p class="max-w-[25rem] text-pretty">Our CI continuously tests Vite changes against downstream projects,
38
+ <p class="max-w-[25rem] text-pretty">Our CI continuously tests Vite changes
39
+ against downstream projects,
37
40
  allowing us to improve Vite with stability and confidence.</p>
38
41
  </div>
39
42
  <div class="px-5 sm:px-10 flex justify-center">
@@ -17,12 +17,13 @@ const installTabs = [
17
17
  <div class="wrapper wrapper--ticks grid md:grid-cols-2 w-full border-nickel md:divide-x">
18
18
  <div class="flex flex-col p-10 justify-between gap-20 items-center md:items-start">
19
19
  <div class="flex flex-col gap-5 items-center md:items-start text-center md:text-left">
20
- <div class="flex items-center gap-2">
20
+ <a class="flex items-center gap-2" href="https://voidzero.dev" target="_blank">
21
21
  <span class="text-grey text-xs font-mono uppercase tracking-wide">By</span>
22
- <img src="../../assets/logos/voidzero-light.svg" alt="VoidZero" class="h-2.5">
23
- </div>
22
+ <img src="@assets/logos/voidzero-light.svg" alt="VoidZero" class="h-2.5">
23
+ </a>
24
24
  <h1 class="text-white text-pretty max-w-[25rem]">The Build Tool for the Web</h1>
25
- <p class="text-white/70 md:text-lg max-w-[27rem] text-pretty">Vite is a blazing fast frontend build tool powering
25
+ <p class="text-white/70 md:text-lg max-w-[27rem] text-pretty">Vite is a blazing fast frontend build tool
26
+ powering
26
27
  the next generation of web applications.</p>
27
28
  <div class="flex items-center gap-5 mt-8">
28
29
  <a href="/guide/" class="button button--primary inline-block w-fit">
@@ -2,10 +2,10 @@
2
2
  <div class="wrapper wrapper--ticks grid md:grid-cols-2 w-full border-nickel divide-x">
3
3
  <div class="flex flex-col p-10 justify-center items-center md:items-start">
4
4
  <div class="flex flex-col gap-5 max-w-[30rem] text-center md:text-left items-center md:items-start">
5
- <div class="flex items-center gap-2">
5
+ <a class="flex items-center gap-2" href="https://voidzero.dev" target="_blank">
6
6
  <span class="text-grey text-xs font-mono uppercase tracking-wide">By</span>
7
7
  <img src="@assets/logos/voidzero-light.svg" alt="VoidZero" class="h-2.5">
8
- </div>
8
+ </a>
9
9
  <h1 class="text-white text-pretty">Next Generation Testing Framework</h1>
10
10
  <p class="text-white/70 text-lg max-w-[25rem] text-pretty">A Vite-native testing framework. It's fast!</p>
11
11
  <div class="flex items-center gap-5 mt-8">
@@ -34,4 +34,4 @@
34
34
  }
35
35
  </style>
36
36
  <script setup lang="ts">
37
- </script>
37
+ </script>
@@ -0,0 +1,14 @@
1
+ export interface Sponsor {
2
+ name: string
3
+ img: string
4
+ url: string
5
+ hasDark?: true
6
+ }
7
+
8
+ export type SponsorSize = 'big' | 'medium' | 'small' | 'avatar'
9
+
10
+ export interface SponsorTier {
11
+ tier: string
12
+ size: SponsorSize
13
+ items: Sponsor[]
14
+ }