@voidzero-dev/vitepress-theme 3.0.2 → 3.2.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/config.js +11 -2
- package/package.json +1 -1
- package/src/assets/vite/vite-featurepanel-2-terminal.png +0 -0
- package/src/components/oss/Sponsors.vue +145 -265
- package/src/components/oss/TrustedBy.vue +8 -33
- package/src/components/oxc/FeatureFormatter.vue +30 -0
- package/src/components/oxc/FeatureLinter.vue +5 -8
- package/src/components/oxc/FeatureMinifier.vue +26 -0
- package/src/components/oxc/FeatureParser.vue +22 -21
- package/src/components/oxc/FeatureResolver.vue +9 -4
- package/src/components/oxc/FeatureToolbar.vue +17 -19
- package/src/components/oxc/FeatureTransformer.vue +8 -7
- package/src/components/oxc/Hero.vue +10 -17
- package/src/components/rolldown/FeatureGrid.vue +35 -39
- package/src/components/rolldown/Hero.vue +6 -6
- package/src/components/vite/Community.vue +1 -1
- package/src/components/vite/FeatureGrid1.vue +12 -9
- package/src/components/vite/FeatureGrid2.vue +7 -4
- package/src/components/vite/Hero.vue +5 -4
- package/src/components/vitest/FeatureGrid.vue +5 -3
- package/src/components/vitest/Hero.vue +3 -3
- package/src/components/vitest/Intro.vue +15 -14
- package/src/oxc.ts +22 -0
- package/src/rolldown.ts +22 -0
- package/src/types/sponsors.ts +14 -0
- package/src/vite.ts +22 -0
- package/src/vitest.ts +22 -0
- package/src/components/oxc/FeatureMinifierFormatter.vue +0 -35
package/config.js
CHANGED
|
@@ -53,8 +53,17 @@ export function extendConfig(c) {
|
|
|
53
53
|
|
|
54
54
|
// inject alias
|
|
55
55
|
v.resolve ??= {};
|
|
56
|
-
v.resolve.alias ??= {};
|
|
57
|
-
|
|
56
|
+
const al = (v.resolve.alias ??= {});
|
|
57
|
+
if (Array.isArray(al)) {
|
|
58
|
+
for (const key in aliases) {
|
|
59
|
+
al.push({
|
|
60
|
+
find: key,
|
|
61
|
+
replacement: aliases[key]
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
Object.assign(v.resolve.alias, aliases);
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
const pkg = "@voidzero-dev/vitepress-theme";
|
|
60
69
|
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -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?:
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
return
|
|
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
|
-
//
|
|
89
|
-
const
|
|
90
|
-
const
|
|
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
|
|
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 [
|
|
109
|
-
const
|
|
110
|
-
const
|
|
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 (!
|
|
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
|
|
71
|
+
const stackedSponsors = computed(() => {
|
|
122
72
|
if (!props.sponsors) return []
|
|
73
|
+
if (!props.sideBySideTiers) return props.sponsors
|
|
123
74
|
|
|
124
|
-
const
|
|
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-
|
|
182
|
-
<div
|
|
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
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
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 &&
|
|
194
|
-
|
|
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
|
|
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.
|
|
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-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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.
|
|
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
|
-
|
|
231
|
-
:
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
v-
|
|
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-
|
|
312
|
-
:class="
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import: 'default'
|
|
9
|
-
})
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
logos: string[]
|
|
6
|
+
}>()
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section id="feature-formatter" class="wrapper grid border-t md:grid-cols-2">
|
|
3
|
+
<div class="px-5 py-6 md:p-10 flex flex-col justify-center gap-15">
|
|
4
|
+
<div class="flex flex-col gap-5">
|
|
5
|
+
<span class="text-grey text-xs font-mono uppercase tracking-wide">Formatter<span class="text-aqua"> (Alpha)</span></span>
|
|
6
|
+
<h4 class="text-white">Oxfmt: Prettier-compatible formatter</h4>
|
|
7
|
+
<p class="text-white/70 text-base max-w-[25rem] text-pretty">Enforce consistent code styles</p>
|
|
8
|
+
<ul class="checkmark-list">
|
|
9
|
+
<li>3x faster than <code class="mx-1 outline-none bg-nickel/50 text-aqua">Biome</code></li>
|
|
10
|
+
<li>35x faster than <code class="mx-1 outline-none bg-nickel/50 text-aqua">Prettier</code></li>
|
|
11
|
+
<li>Tailwind class sorting support</li>
|
|
12
|
+
</ul>
|
|
13
|
+
<a href="/docs/guide/usage/formatter" class="button w-fit mt-6">Usage Guide</a>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex flex-col">
|
|
17
|
+
<div class="h-full overflow-clip flex justify-end items-center py-5 px-5 md:py-20 md:pl-10 md:pr-0">
|
|
18
|
+
<img loading="lazy" src="@assets/oxc/oxc-formatter-terminal.png" alt="Linter" class="[mask-image:linear-gradient(to_bottom,black_50%,transparent),linear-gradient(to_right,black_50%,transparent)] [mask-composite:intersect] [-webkit-mask-composite:source-in]">
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</section>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
.card-bg {
|
|
26
|
+
background-image: url('@assets/oxc/oxc-feature-background.jpg');
|
|
27
|
+
background-size: cover;
|
|
28
|
+
background-position: center;
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
1
|
<template>
|
|
6
|
-
<section id="feature-linter" class="wrapper
|
|
2
|
+
<section id="feature-linter" class="wrapper grid md:grid-cols-2">
|
|
7
3
|
<div class="px-5 py-6 md:p-10 flex flex-col justify-center gap-15">
|
|
8
4
|
<div class="flex flex-col gap-5">
|
|
9
5
|
<span class="text-grey text-xs font-mono uppercase tracking-wide">Linter</span>
|
|
10
|
-
<h4 class="text-white">Oxlint:
|
|
6
|
+
<h4 class="text-white">Oxlint: ESLint-compatible linter</h4>
|
|
11
7
|
<p class="text-white/70 text-base max-w-[25rem] text-pretty">Catch bugs before they make it to production</p>
|
|
12
8
|
<ul class="checkmark-list">
|
|
13
9
|
<li>50~100x faster than <code class="mx-1 outline-none bg-nickel/50 text-aqua">ESLint</code></li>
|
|
14
10
|
<li>570+ rules and growing</li>
|
|
15
|
-
<li>
|
|
11
|
+
<li>True type-aware Linting powered by <code class="mx-1 outline-none bg-nickel/50 text-aqua">tsgo</code></li>
|
|
12
|
+
<li>Support for <code class="mx-1 outline-none bg-nickel/50 text-aqua">ESLint</code> JS Plugins</li>
|
|
16
13
|
</ul>
|
|
17
14
|
<a href="/docs/guide/usage/linter" class="button w-fit mt-6">Usage Guide</a>
|
|
18
15
|
</div>
|
|
@@ -31,4 +28,4 @@
|
|
|
31
28
|
background-size: cover;
|
|
32
29
|
background-position: center;
|
|
33
30
|
}
|
|
34
|
-
</style>
|
|
31
|
+
</style>
|