create-velocity-astro 1.4.2 → 1.5.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 +1 -1
- package/templates/i18n/src/components/hero/Hero.astro +154 -248
- package/templates/i18n/src/components/landing/CTA.astro +35 -50
- package/templates/i18n/src/components/layout/Footer.astro +1 -1
- package/templates/i18n/src/components/layout/Header.astro +1 -1
- package/templates/i18n/src/i18n/helpers.ts +10 -7
- package/templates/i18n/src/pages/[lang]/[...about].astro +9 -13
- package/templates/i18n/src/pages/[lang]/[...contact].astro +9 -13
- package/templates/i18n/src/pages/[lang]/index.astro +9 -15
- package/templates/i18n/src/pages/about.astro +9 -13
- package/templates/i18n/src/pages/contact.astro +9 -13
- package/templates/i18n/src/pages/index.astro +9 -15
package/package.json
CHANGED
|
@@ -1,248 +1,154 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
-
import { cn } from '@/lib/cn';
|
|
4
|
-
|
|
5
|
-
interface Props extends HTMLAttributes<'section'> {
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
blobPosition
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)}
|
|
156
|
-
{hasBadgeSlot ? (
|
|
157
|
-
<slot name="badge" />
|
|
158
|
-
) : (
|
|
159
|
-
<span class={cn(
|
|
160
|
-
'text-xs font-medium',
|
|
161
|
-
background === 'invert' ? 'text-foreground' : 'text-foreground-secondary'
|
|
162
|
-
)}>{badge}</span>
|
|
163
|
-
)}
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
166
|
-
|
|
167
|
-
{/* Title */}
|
|
168
|
-
{(hasTitleSlot || title) && (
|
|
169
|
-
<TitleElement class={cn(
|
|
170
|
-
'font-display mb-6 text-5xl leading-[1.1] font-bold tracking-tight text-balance md:text-6xl lg:text-7xl',
|
|
171
|
-
background === 'invert' ? 'text-on-invert' : 'text-foreground'
|
|
172
|
-
)}>
|
|
173
|
-
{hasTitleSlot ? (
|
|
174
|
-
<slot name="title" />
|
|
175
|
-
) : (
|
|
176
|
-
<Fragment set:html={processTitle(title!, titleHighlight)} />
|
|
177
|
-
)}
|
|
178
|
-
</TitleElement>
|
|
179
|
-
)}
|
|
180
|
-
|
|
181
|
-
{/* Description */}
|
|
182
|
-
{(hasDescriptionSlot || description) && (
|
|
183
|
-
<p class={cn(
|
|
184
|
-
'mb-8 max-w-xl text-lg leading-relaxed',
|
|
185
|
-
background === 'invert' ? 'text-on-invert/70' : 'text-foreground-muted',
|
|
186
|
-
align === 'center' && 'mx-auto'
|
|
187
|
-
)}>
|
|
188
|
-
{hasDescriptionSlot ? (
|
|
189
|
-
<slot name="description" />
|
|
190
|
-
) : (
|
|
191
|
-
description
|
|
192
|
-
)}
|
|
193
|
-
</p>
|
|
194
|
-
)}
|
|
195
|
-
|
|
196
|
-
{/* Actions */}
|
|
197
|
-
{hasActionsSlot && (
|
|
198
|
-
<div class={cn(
|
|
199
|
-
'flex w-full flex-col gap-4 sm:w-auto sm:flex-row',
|
|
200
|
-
align === 'center' && 'justify-center'
|
|
201
|
-
)}>
|
|
202
|
-
<slot name="actions" />
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
205
|
-
|
|
206
|
-
{/* Social Proof */}
|
|
207
|
-
{hasSocialProofSlot && (
|
|
208
|
-
<div class={cn(
|
|
209
|
-
'mt-8 flex items-center gap-4 text-sm',
|
|
210
|
-
background === 'invert' ? 'text-on-invert/60' : 'text-foreground-subtle'
|
|
211
|
-
)}>
|
|
212
|
-
<slot name="social-proof" />
|
|
213
|
-
</div>
|
|
214
|
-
)}
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
{/* Aside Column (for split layout) */}
|
|
218
|
-
{layout === 'split' && hasAsideSlot && (
|
|
219
|
-
<div class="relative z-10 w-full">
|
|
220
|
-
<slot name="aside" />
|
|
221
|
-
{/* Decorative blob */}
|
|
222
|
-
{showBlob && (
|
|
223
|
-
<div
|
|
224
|
-
class={cn(
|
|
225
|
-
'bg-brand-200/20 pointer-events-none absolute h-64 w-64 rounded-full blur-3xl',
|
|
226
|
-
blobPositions[blobPosition]
|
|
227
|
-
)}
|
|
228
|
-
aria-hidden="true"
|
|
229
|
-
/>
|
|
230
|
-
)}
|
|
231
|
-
</div>
|
|
232
|
-
)}
|
|
233
|
-
|
|
234
|
-
{/* Decorative blob for single layout */}
|
|
235
|
-
{layout === 'single' && showBlob && (
|
|
236
|
-
<div
|
|
237
|
-
class={cn(
|
|
238
|
-
'bg-brand-200/20 pointer-events-none absolute h-64 w-64 rounded-full blur-3xl',
|
|
239
|
-
blobPositions[blobPosition]
|
|
240
|
-
)}
|
|
241
|
-
aria-hidden="true"
|
|
242
|
-
/>
|
|
243
|
-
)}
|
|
244
|
-
</div>
|
|
245
|
-
|
|
246
|
-
{/* Default slot for additional content */}
|
|
247
|
-
<slot />
|
|
248
|
-
</section>
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import { cn } from '@/lib/cn';
|
|
4
|
+
|
|
5
|
+
interface Props extends HTMLAttributes<'section'> {
|
|
6
|
+
/** Layout mode: centered single column or split two-column */
|
|
7
|
+
layout?: 'centered' | 'split';
|
|
8
|
+
/** Vertical padding size */
|
|
9
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
10
|
+
/** Show background grid pattern */
|
|
11
|
+
showGrid?: boolean;
|
|
12
|
+
/** Show decorative gradient blob */
|
|
13
|
+
showBlob?: boolean;
|
|
14
|
+
/** Blob position (only applies when showBlob is true) */
|
|
15
|
+
blobPosition?: 'left' | 'right' | 'center';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
layout = 'centered',
|
|
20
|
+
size = 'lg',
|
|
21
|
+
showGrid = false,
|
|
22
|
+
showBlob = false,
|
|
23
|
+
blobPosition = 'right',
|
|
24
|
+
class: className,
|
|
25
|
+
...attrs
|
|
26
|
+
} = Astro.props;
|
|
27
|
+
|
|
28
|
+
// Check which slots are provided
|
|
29
|
+
const hasBadgeSlot = Astro.slots.has('badge');
|
|
30
|
+
const hasTitleSlot = Astro.slots.has('title');
|
|
31
|
+
const hasDescriptionSlot = Astro.slots.has('description');
|
|
32
|
+
const hasActionsSlot = Astro.slots.has('actions');
|
|
33
|
+
const hasAsideSlot = Astro.slots.has('aside');
|
|
34
|
+
|
|
35
|
+
// Size (padding) variants - asymmetric: more top padding for header clearance
|
|
36
|
+
const sizes: Record<NonNullable<Props['size']>, string> = {
|
|
37
|
+
sm: 'pt-[var(--space-page-top-sm)] pb-[var(--space-section-sm)]',
|
|
38
|
+
md: 'pt-[var(--space-page-top)] pb-[var(--space-section-md)]',
|
|
39
|
+
lg: 'pt-[calc(var(--space-page-top)+var(--space-8))] pb-[var(--space-section-lg)]',
|
|
40
|
+
xl: 'pt-[calc(var(--space-page-top)+var(--space-16))] pb-[var(--space-section-xl)]',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Blob position variants
|
|
44
|
+
const blobPositions: Record<NonNullable<Props['blobPosition']>, string> = {
|
|
45
|
+
left: '-left-20 -top-20',
|
|
46
|
+
right: '-right-20 -top-20',
|
|
47
|
+
center: 'left-1/2 -translate-x-1/2 -top-20',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Compute alignment based on layout
|
|
51
|
+
const alignment = layout === 'centered' ? 'text-center items-center' : 'text-left items-start';
|
|
52
|
+
|
|
53
|
+
// Compute classes
|
|
54
|
+
const sectionClasses = cn(
|
|
55
|
+
'relative overflow-hidden bg-background',
|
|
56
|
+
sizes[size],
|
|
57
|
+
className
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const contentClasses = cn(
|
|
61
|
+
'z-10 flex flex-col',
|
|
62
|
+
alignment
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const gridClasses = cn(
|
|
66
|
+
'mx-auto grid max-w-6xl grid-cols-1 items-center gap-[var(--space-section-gap)] px-6',
|
|
67
|
+
layout === 'split' && 'lg:grid-cols-2 lg:gap-[var(--space-section-gap)]'
|
|
68
|
+
);
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
<section class={sectionClasses} {...attrs}>
|
|
72
|
+
{/* Background grid pattern */}
|
|
73
|
+
{showGrid && (
|
|
74
|
+
<div
|
|
75
|
+
class="bg-grid-pattern pointer-events-none absolute inset-0 [mask-image:linear-gradient(to_bottom,white,transparent)] opacity-40"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
<div class={gridClasses}>
|
|
81
|
+
{/* Content Column */}
|
|
82
|
+
<div class={cn(contentClasses, 'order-1 lg:order-none')}>
|
|
83
|
+
{/* Badge Slot */}
|
|
84
|
+
{hasBadgeSlot && (
|
|
85
|
+
<div class="mb-[var(--space-heading-gap)]">
|
|
86
|
+
<slot name="badge" />
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{/* Title Slot */}
|
|
91
|
+
{hasTitleSlot && (
|
|
92
|
+
<div class={cn(
|
|
93
|
+
'mb-[var(--space-heading-gap)]',
|
|
94
|
+
'[&>h1]:font-display [&>h1]:text-5xl [&>h1]:leading-[1.1] [&>h1]:font-bold [&>h1]:tracking-tight [&>h1]:text-balance [&>h1]:text-foreground md:[&>h1]:text-6xl lg:[&>h1]:text-7xl',
|
|
95
|
+
'[&>h2]:font-display [&>h2]:text-4xl [&>h2]:leading-[1.1] [&>h2]:font-bold [&>h2]:tracking-tight [&>h2]:text-balance [&>h2]:text-foreground md:[&>h2]:text-5xl lg:[&>h2]:text-6xl'
|
|
96
|
+
)}>
|
|
97
|
+
<slot name="title" />
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
{/* Description Slot */}
|
|
102
|
+
{hasDescriptionSlot && (
|
|
103
|
+
<div class={cn(
|
|
104
|
+
'mb-[var(--space-stack-lg)] max-w-xl text-lg leading-relaxed text-foreground-muted',
|
|
105
|
+
'[&>p]:text-lg [&>p]:leading-relaxed [&>p]:text-foreground-muted',
|
|
106
|
+
layout === 'centered' && 'mx-auto'
|
|
107
|
+
)}>
|
|
108
|
+
<slot name="description" />
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{/* Actions Slot */}
|
|
113
|
+
{hasActionsSlot && (
|
|
114
|
+
<div class={cn(
|
|
115
|
+
'flex w-full flex-col gap-4 sm:w-auto sm:flex-row',
|
|
116
|
+
layout === 'centered' && 'justify-center'
|
|
117
|
+
)}>
|
|
118
|
+
<slot name="actions" />
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{/* Default slot for additional content (social proof, etc.) */}
|
|
123
|
+
<slot />
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Aside Column (for split layout) */}
|
|
127
|
+
{layout === 'split' && hasAsideSlot && (
|
|
128
|
+
<div class="relative z-10 w-full order-2 lg:order-none">
|
|
129
|
+
<slot name="aside" />
|
|
130
|
+
{/* Decorative blob for split layout */}
|
|
131
|
+
{showBlob && (
|
|
132
|
+
<div
|
|
133
|
+
class={cn(
|
|
134
|
+
'bg-brand-200/20 pointer-events-none absolute h-64 w-64 rounded-full blur-3xl',
|
|
135
|
+
blobPositions[blobPosition]
|
|
136
|
+
)}
|
|
137
|
+
aria-hidden="true"
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{/* Decorative blob for centered layout */}
|
|
144
|
+
{layout === 'centered' && showBlob && (
|
|
145
|
+
<div
|
|
146
|
+
class={cn(
|
|
147
|
+
'bg-brand-200/20 pointer-events-none absolute h-64 w-64 rounded-full blur-3xl',
|
|
148
|
+
blobPositions[blobPosition]
|
|
149
|
+
)}
|
|
150
|
+
aria-hidden="true"
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
@@ -32,18 +32,6 @@ interface Props extends HTMLAttributes<'section'> {
|
|
|
32
32
|
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
33
33
|
/** Max width of content */
|
|
34
34
|
maxWidth?: 'sm' | 'md' | 'lg' | 'xl';
|
|
35
|
-
/** Show logo */
|
|
36
|
-
showLogo?: boolean;
|
|
37
|
-
/** Logo size */
|
|
38
|
-
logoSize?: 'md' | 'lg' | 'xl' | '2xl';
|
|
39
|
-
/** Heading text (alternative to slot) */
|
|
40
|
-
heading?: string;
|
|
41
|
-
/** Text to highlight in brand color (within heading) */
|
|
42
|
-
headingHighlight?: string;
|
|
43
|
-
/** Description text (alternative to slot) */
|
|
44
|
-
description?: string;
|
|
45
|
-
/** Content alignment */
|
|
46
|
-
align?: 'center' | 'left';
|
|
47
35
|
/** Show copy command button */
|
|
48
36
|
showCopyCommand?: boolean;
|
|
49
37
|
/** Command to copy (e.g., npm create velocity@latest) */
|
|
@@ -55,12 +43,6 @@ const {
|
|
|
55
43
|
variant = 'default',
|
|
56
44
|
size = 'lg',
|
|
57
45
|
maxWidth = 'lg',
|
|
58
|
-
showLogo = true,
|
|
59
|
-
logoSize = '2xl',
|
|
60
|
-
heading,
|
|
61
|
-
headingHighlight,
|
|
62
|
-
description,
|
|
63
|
-
align = 'center',
|
|
64
46
|
showCopyCommand = true,
|
|
65
47
|
command,
|
|
66
48
|
id = 'cta',
|
|
@@ -95,10 +77,10 @@ const highlightStyles = {
|
|
|
95
77
|
};
|
|
96
78
|
|
|
97
79
|
const sizes = {
|
|
98
|
-
sm: 'py-
|
|
99
|
-
md: 'py-
|
|
100
|
-
lg: 'py-
|
|
101
|
-
xl: 'py-
|
|
80
|
+
sm: 'py-[var(--space-section-sm)]',
|
|
81
|
+
md: 'py-[var(--space-section-md)]',
|
|
82
|
+
lg: 'py-[var(--space-section-lg)]',
|
|
83
|
+
xl: 'py-[var(--space-section-xl)]',
|
|
102
84
|
};
|
|
103
85
|
|
|
104
86
|
const maxWidths = {
|
|
@@ -108,15 +90,10 @@ const maxWidths = {
|
|
|
108
90
|
xl: 'max-w-4xl',
|
|
109
91
|
};
|
|
110
92
|
|
|
111
|
-
const alignStyles = {
|
|
112
|
-
center: 'text-center mx-auto',
|
|
113
|
-
left: 'text-left',
|
|
114
|
-
};
|
|
115
|
-
|
|
116
93
|
// Use translations for default content
|
|
117
|
-
const displayHeading =
|
|
118
|
-
const displayHighlight =
|
|
119
|
-
const displayDescription =
|
|
94
|
+
const displayHeading = t('cta.title');
|
|
95
|
+
const displayHighlight = t('cta.titleHighlight');
|
|
96
|
+
const displayDescription = t('cta.description');
|
|
120
97
|
const displayCommand = command || t('cta.command');
|
|
121
98
|
const copiedText = t('common.copied');
|
|
122
99
|
const docsLabel = t('cta.docs');
|
|
@@ -127,6 +104,7 @@ function processHeading(text: string, highlight?: string): string {
|
|
|
127
104
|
return text.replace(highlight, `<span class="${highlightStyles[variant]}">${highlight}</span>`);
|
|
128
105
|
}
|
|
129
106
|
|
|
107
|
+
const hasLogoSlot = Astro.slots.has('logo');
|
|
130
108
|
const hasHeadingSlot = Astro.slots.has('heading');
|
|
131
109
|
const hasDescriptionSlot = Astro.slots.has('description');
|
|
132
110
|
const hasActionsSlot = Astro.slots.has('actions');
|
|
@@ -141,54 +119,61 @@ const buttonId = `copy-command-${Math.random().toString(36).slice(2, 9)}`;
|
|
|
141
119
|
{...attrs}
|
|
142
120
|
>
|
|
143
121
|
<div class="mx-auto max-w-6xl px-6">
|
|
144
|
-
<div class={cn(maxWidths[maxWidth],
|
|
145
|
-
{
|
|
122
|
+
<div class={cn(maxWidths[maxWidth], 'text-center mx-auto')}>
|
|
123
|
+
{/* Logo Slot */}
|
|
124
|
+
{hasLogoSlot ? (
|
|
125
|
+
<div class="mb-[var(--space-stack-lg)]">
|
|
126
|
+
<slot name="logo" />
|
|
127
|
+
</div>
|
|
128
|
+
) : (
|
|
146
129
|
<Logo
|
|
147
|
-
size=
|
|
130
|
+
size="2xl"
|
|
148
131
|
forceDark={isInvert}
|
|
149
|
-
class=
|
|
132
|
+
class="mb-[var(--space-stack-lg)] mx-auto"
|
|
150
133
|
/>
|
|
151
134
|
)}
|
|
152
135
|
|
|
136
|
+
{/* Heading Slot */}
|
|
153
137
|
{hasHeadingSlot ? (
|
|
154
|
-
<
|
|
155
|
-
'
|
|
156
|
-
|
|
138
|
+
<div class={cn(
|
|
139
|
+
'mb-[var(--space-heading-gap)]',
|
|
140
|
+
'[&>h2]:font-display [&>h2]:text-3xl md:[&>h2]:text-4xl lg:[&>h2]:text-5xl [&>h2]:font-bold [&>h2]:text-balance',
|
|
141
|
+
isInvert ? '[&>h2]:text-on-invert' : '[&>h2]:text-foreground'
|
|
157
142
|
)}>
|
|
158
143
|
<slot name="heading" />
|
|
159
|
-
</
|
|
144
|
+
</div>
|
|
160
145
|
) : (
|
|
161
146
|
<h2
|
|
162
147
|
class={cn(
|
|
163
|
-
'font-display text-3xl md:text-4xl lg:text-5xl font-bold text-balance mb-
|
|
148
|
+
'font-display text-3xl md:text-4xl lg:text-5xl font-bold text-balance mb-[var(--space-heading-gap)]',
|
|
164
149
|
headingStyles[variant]
|
|
165
150
|
)}
|
|
166
151
|
set:html={processHeading(displayHeading, displayHighlight)}
|
|
167
152
|
/>
|
|
168
153
|
)}
|
|
169
154
|
|
|
155
|
+
{/* Description Slot */}
|
|
170
156
|
{hasDescriptionSlot ? (
|
|
171
|
-
<
|
|
157
|
+
<div class={cn(
|
|
158
|
+
'text-lg md:text-xl mb-[var(--space-stack-lg)]',
|
|
159
|
+
'[&>p]:text-lg md:[&>p]:text-xl',
|
|
160
|
+
isInvert ? 'text-on-invert-secondary [&>p]:text-on-invert-secondary' : 'text-foreground-muted [&>p]:text-foreground-muted'
|
|
161
|
+
)}>
|
|
172
162
|
<slot name="description" />
|
|
173
|
-
</
|
|
163
|
+
</div>
|
|
174
164
|
) : (
|
|
175
|
-
<p class={cn('text-lg md:text-xl mb-
|
|
165
|
+
<p class={cn('text-lg md:text-xl mb-[var(--space-stack-lg)]', descriptionStyles[variant])}>
|
|
176
166
|
{displayDescription}
|
|
177
167
|
</p>
|
|
178
168
|
)}
|
|
179
169
|
|
|
170
|
+
{/* Actions Slot */}
|
|
180
171
|
{hasActionsSlot ? (
|
|
181
|
-
<div class=
|
|
182
|
-
'flex flex-col gap-4 sm:flex-row',
|
|
183
|
-
align === 'center' && 'items-center justify-center'
|
|
184
|
-
)}>
|
|
172
|
+
<div class="flex flex-col gap-4 sm:flex-row items-center justify-center">
|
|
185
173
|
<slot name="actions" />
|
|
186
174
|
</div>
|
|
187
175
|
) : showCopyCommand && (
|
|
188
|
-
<div class=
|
|
189
|
-
'flex flex-col gap-4 sm:flex-row',
|
|
190
|
-
align === 'center' && 'items-center justify-center'
|
|
191
|
-
)}>
|
|
176
|
+
<div class="flex flex-col gap-4 sm:flex-row items-center justify-center">
|
|
192
177
|
<button
|
|
193
178
|
type="button"
|
|
194
179
|
id={buttonId}
|
|
@@ -100,7 +100,7 @@ const t = useTranslations(locale);
|
|
|
100
100
|
// Get navigation items from i18n routes or custom nav
|
|
101
101
|
const defaultNav = getNavRoutes(locale);
|
|
102
102
|
const navItems: NavItem[] = nav || defaultNav.map(route => ({
|
|
103
|
-
label: route.label,
|
|
103
|
+
label: t(route.label as any) || route.label,
|
|
104
104
|
href: route.path,
|
|
105
105
|
}));
|
|
106
106
|
|
|
@@ -111,7 +111,7 @@ const isInvert = colorScheme === 'invert';
|
|
|
111
111
|
// Get navigation items from i18n routes or custom nav
|
|
112
112
|
const defaultNav = getNavRoutes(locale);
|
|
113
113
|
const navItems: NavItem[] = nav || [...extraNav, ...defaultNav.map(route => ({
|
|
114
|
-
label: route.label,
|
|
114
|
+
label: t(route.label as any) || route.label,
|
|
115
115
|
href: route.path,
|
|
116
116
|
}))];
|
|
117
117
|
|
|
@@ -241,29 +241,32 @@ export type NavRoute = {
|
|
|
241
241
|
routeId: RouteId;
|
|
242
242
|
label: string;
|
|
243
243
|
order: number;
|
|
244
|
+
path: string;
|
|
244
245
|
};
|
|
245
246
|
|
|
246
247
|
/**
|
|
247
248
|
* Get routes that should appear in navigation, sorted by order
|
|
248
249
|
*
|
|
249
|
-
* @
|
|
250
|
+
* @param locale - The locale to get paths for (defaults to defaultLocale)
|
|
251
|
+
* @returns Array of navigation routes with their translation keys and localized paths
|
|
250
252
|
*
|
|
251
253
|
* @example
|
|
252
|
-
* const navRoutes = getNavRoutes();
|
|
254
|
+
* const navRoutes = getNavRoutes('en');
|
|
253
255
|
* // → [
|
|
254
|
-
* // { routeId: 'components', label: 'nav.components', order: 1 },
|
|
255
|
-
* // { routeId: 'blog', label: 'nav.blog', order: 2 },
|
|
256
|
-
* // { routeId: 'about', label: 'nav.about', order: 3 },
|
|
257
|
-
* // { routeId: 'contact', label: 'nav.contact', order: 4 },
|
|
256
|
+
* // { routeId: 'components', label: 'nav.components', order: 1, path: '/components' },
|
|
257
|
+
* // { routeId: 'blog', label: 'nav.blog', order: 2, path: '/blog' },
|
|
258
|
+
* // { routeId: 'about', label: 'nav.about', order: 3, path: '/about' },
|
|
259
|
+
* // { routeId: 'contact', label: 'nav.contact', order: 4, path: '/contact' },
|
|
258
260
|
* // ]
|
|
259
261
|
*/
|
|
260
|
-
export function getNavRoutes(): NavRoute[] {
|
|
262
|
+
export function getNavRoutes(locale: Locale = defaultLocale): NavRoute[] {
|
|
261
263
|
return Object.entries(routes)
|
|
262
264
|
.filter(([_, route]) => route.nav?.show === true)
|
|
263
265
|
.map(([routeId, route]) => ({
|
|
264
266
|
routeId: routeId as RouteId,
|
|
265
267
|
label: route.nav!.label,
|
|
266
268
|
order: route.nav!.order,
|
|
269
|
+
path: getLocalizedPath(routeId as RouteId, locale),
|
|
267
270
|
}))
|
|
268
271
|
.sort((a, b) => a.order - b.order);
|
|
269
272
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import PageLayout from '@/layouts/PageLayout.astro';
|
|
7
7
|
import Icon from '@/components/ui/Icon.astro';
|
|
8
|
+
import Badge from '@/components/ui/Badge.astro';
|
|
8
9
|
import Card from '@/components/ui/Card.astro';
|
|
9
10
|
import { Hero } from '@/components/hero';
|
|
10
11
|
import { locales, defaultLocale, isValidLocale, type Locale } from '@/i18n/config';
|
|
@@ -42,21 +43,16 @@ const t = useTranslations(locale);
|
|
|
42
43
|
lang={locale}
|
|
43
44
|
routeId="about"
|
|
44
45
|
>
|
|
45
|
-
<Hero
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
showGrid
|
|
50
|
-
badge={t('about.hero.badge')}
|
|
51
|
-
badgeIcon
|
|
52
|
-
titleElement="h1"
|
|
53
|
-
>
|
|
54
|
-
<Fragment slot="title">
|
|
46
|
+
<Hero layout="centered" size="sm" showGrid>
|
|
47
|
+
<Badge slot="badge" pill pulse>{t('about.hero.badge')}</Badge>
|
|
48
|
+
|
|
49
|
+
<h1 slot="title">
|
|
55
50
|
{t('about.hero.title')} <span class="text-brand-500">{t('about.hero.titleHighlight')}</span>
|
|
56
|
-
</
|
|
57
|
-
|
|
51
|
+
</h1>
|
|
52
|
+
|
|
53
|
+
<p slot="description">
|
|
58
54
|
{t('about.hero.description')}
|
|
59
|
-
</
|
|
55
|
+
</p>
|
|
60
56
|
</Hero>
|
|
61
57
|
|
|
62
58
|
<!-- Mission Section -->
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import PageLayout from '@/layouts/PageLayout.astro';
|
|
7
7
|
import Icon from '@/components/ui/Icon.astro';
|
|
8
|
+
import Badge from '@/components/ui/Badge.astro';
|
|
8
9
|
import Button from '@/components/ui/Button.astro';
|
|
9
10
|
import Input from '@/components/ui/Input.astro';
|
|
10
11
|
import Textarea from '@/components/ui/Textarea.astro';
|
|
@@ -46,21 +47,16 @@ const t = useTranslations(locale);
|
|
|
46
47
|
lang={locale}
|
|
47
48
|
routeId="contact"
|
|
48
49
|
>
|
|
49
|
-
<Hero
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
showGrid
|
|
54
|
-
badge={t('contact.hero.badge')}
|
|
55
|
-
badgeIcon
|
|
56
|
-
titleElement="h1"
|
|
57
|
-
>
|
|
58
|
-
<Fragment slot="title">
|
|
50
|
+
<Hero layout="centered" size="sm" showGrid>
|
|
51
|
+
<Badge slot="badge" pill pulse>{t('contact.hero.badge')}</Badge>
|
|
52
|
+
|
|
53
|
+
<h1 slot="title">
|
|
59
54
|
{t('contact.hero.title')} <span class="text-brand-500">{t('contact.hero.titleHighlight')}</span>
|
|
60
|
-
</
|
|
61
|
-
|
|
55
|
+
</h1>
|
|
56
|
+
|
|
57
|
+
<p slot="description">
|
|
62
58
|
{t('contact.hero.description')}
|
|
63
|
-
</
|
|
59
|
+
</p>
|
|
64
60
|
</Hero>
|
|
65
61
|
|
|
66
62
|
<!-- Contact Form Section -->
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import LandingLayout from '@/layouts/LandingLayout.astro';
|
|
7
7
|
import { Hero } from '@/components/hero';
|
|
8
8
|
import Button from '@/components/ui/Button.astro';
|
|
9
|
+
import Badge from '@/components/ui/Badge.astro';
|
|
9
10
|
import Icon from '@/components/ui/Icon.astro';
|
|
10
11
|
import SocialProof from '@/components/ui/SocialProof.astro';
|
|
11
12
|
import TerminalDemo from '@/components/ui/TerminalDemo.tsx';
|
|
@@ -44,23 +45,16 @@ const ctaLink = '#cta';
|
|
|
44
45
|
lang={locale}
|
|
45
46
|
routeId="home"
|
|
46
47
|
>
|
|
47
|
-
<Hero
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
showGrid
|
|
52
|
-
showBlob
|
|
53
|
-
blobPosition="right"
|
|
54
|
-
badge={t('hero.badge')}
|
|
55
|
-
badgeIcon
|
|
56
|
-
>
|
|
57
|
-
<Fragment slot="title">
|
|
48
|
+
<Hero layout="split" size="lg" showGrid showBlob blobPosition="right">
|
|
49
|
+
<Badge slot="badge" pill pulse>{t('hero.badge')}</Badge>
|
|
50
|
+
|
|
51
|
+
<h1 slot="title">
|
|
58
52
|
{t('hero.title')} <span class="text-brand-500">{t('hero.titleHighlight')}</span>
|
|
59
|
-
</
|
|
53
|
+
</h1>
|
|
60
54
|
|
|
61
|
-
<
|
|
55
|
+
<p slot="description">
|
|
62
56
|
{t('hero.description')}
|
|
63
|
-
</
|
|
57
|
+
</p>
|
|
64
58
|
|
|
65
59
|
<Fragment slot="actions">
|
|
66
60
|
<Button size="lg" href={ctaLink}>
|
|
@@ -81,7 +75,7 @@ const ctaLink = '#cta';
|
|
|
81
75
|
<TerminalDemo slot="aside" client:load />
|
|
82
76
|
|
|
83
77
|
<SocialProof
|
|
84
|
-
|
|
78
|
+
class="mt-[var(--space-stack-lg)]"
|
|
85
79
|
text={t('hero.socialProof')}
|
|
86
80
|
avatars={[
|
|
87
81
|
'https://i.pravatar.cc/150?img=12',
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import PageLayout from '@/layouts/PageLayout.astro';
|
|
7
7
|
import Icon from '@/components/ui/Icon.astro';
|
|
8
|
+
import Badge from '@/components/ui/Badge.astro';
|
|
8
9
|
import Card from '@/components/ui/Card.astro';
|
|
9
10
|
import { Hero } from '@/components/hero';
|
|
10
11
|
import { defaultLocale } from '@/i18n/config';
|
|
@@ -20,21 +21,16 @@ const t = useTranslations(locale);
|
|
|
20
21
|
lang={locale}
|
|
21
22
|
routeId="about"
|
|
22
23
|
>
|
|
23
|
-
<Hero
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
showGrid
|
|
28
|
-
badge={t('about.hero.badge')}
|
|
29
|
-
badgeIcon
|
|
30
|
-
titleElement="h1"
|
|
31
|
-
>
|
|
32
|
-
<Fragment slot="title">
|
|
24
|
+
<Hero layout="centered" size="sm" showGrid>
|
|
25
|
+
<Badge slot="badge" pill pulse>{t('about.hero.badge')}</Badge>
|
|
26
|
+
|
|
27
|
+
<h1 slot="title">
|
|
33
28
|
{t('about.hero.title')} <span class="text-brand-500">{t('about.hero.titleHighlight')}</span>
|
|
34
|
-
</
|
|
35
|
-
|
|
29
|
+
</h1>
|
|
30
|
+
|
|
31
|
+
<p slot="description">
|
|
36
32
|
{t('about.hero.description')}
|
|
37
|
-
</
|
|
33
|
+
</p>
|
|
38
34
|
</Hero>
|
|
39
35
|
|
|
40
36
|
<!-- Mission Section -->
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import PageLayout from '@/layouts/PageLayout.astro';
|
|
7
7
|
import Icon from '@/components/ui/Icon.astro';
|
|
8
|
+
import Badge from '@/components/ui/Badge.astro';
|
|
8
9
|
import Button from '@/components/ui/Button.astro';
|
|
9
10
|
import Input from '@/components/ui/Input.astro';
|
|
10
11
|
import Textarea from '@/components/ui/Textarea.astro';
|
|
@@ -24,21 +25,16 @@ const t = useTranslations(locale);
|
|
|
24
25
|
lang={locale}
|
|
25
26
|
routeId="contact"
|
|
26
27
|
>
|
|
27
|
-
<Hero
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
showGrid
|
|
32
|
-
badge={t('contact.hero.badge')}
|
|
33
|
-
badgeIcon
|
|
34
|
-
titleElement="h1"
|
|
35
|
-
>
|
|
36
|
-
<Fragment slot="title">
|
|
28
|
+
<Hero layout="centered" size="sm" showGrid>
|
|
29
|
+
<Badge slot="badge" pill pulse>{t('contact.hero.badge')}</Badge>
|
|
30
|
+
|
|
31
|
+
<h1 slot="title">
|
|
37
32
|
{t('contact.hero.title')} <span class="text-brand-500">{t('contact.hero.titleHighlight')}</span>
|
|
38
|
-
</
|
|
39
|
-
|
|
33
|
+
</h1>
|
|
34
|
+
|
|
35
|
+
<p slot="description">
|
|
40
36
|
{t('contact.hero.description')}
|
|
41
|
-
</
|
|
37
|
+
</p>
|
|
42
38
|
</Hero>
|
|
43
39
|
|
|
44
40
|
<!-- Contact Form Section -->
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import LandingLayout from '@/layouts/LandingLayout.astro';
|
|
7
7
|
import { Hero } from '@/components/hero';
|
|
8
8
|
import Button from '@/components/ui/Button.astro';
|
|
9
|
+
import Badge from '@/components/ui/Badge.astro';
|
|
9
10
|
import Icon from '@/components/ui/Icon.astro';
|
|
10
11
|
import SocialProof from '@/components/ui/SocialProof.astro';
|
|
11
12
|
import TerminalDemo from '@/components/ui/TerminalDemo.tsx';
|
|
@@ -29,23 +30,16 @@ const ctaLink = '#cta';
|
|
|
29
30
|
lang={locale}
|
|
30
31
|
routeId="home"
|
|
31
32
|
>
|
|
32
|
-
<Hero
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
showGrid
|
|
37
|
-
showBlob
|
|
38
|
-
blobPosition="right"
|
|
39
|
-
badge={t('hero.badge')}
|
|
40
|
-
badgeIcon
|
|
41
|
-
>
|
|
42
|
-
<Fragment slot="title">
|
|
33
|
+
<Hero layout="split" size="lg" showGrid showBlob blobPosition="right">
|
|
34
|
+
<Badge slot="badge" pill pulse>{t('hero.badge')}</Badge>
|
|
35
|
+
|
|
36
|
+
<h1 slot="title">
|
|
43
37
|
{t('hero.title')} <span class="text-brand-500">{t('hero.titleHighlight')}</span>
|
|
44
|
-
</
|
|
38
|
+
</h1>
|
|
45
39
|
|
|
46
|
-
<
|
|
40
|
+
<p slot="description">
|
|
47
41
|
{t('hero.description')}
|
|
48
|
-
</
|
|
42
|
+
</p>
|
|
49
43
|
|
|
50
44
|
<Fragment slot="actions">
|
|
51
45
|
<Button size="lg" href={ctaLink}>
|
|
@@ -66,7 +60,7 @@ const ctaLink = '#cta';
|
|
|
66
60
|
<TerminalDemo slot="aside" client:load />
|
|
67
61
|
|
|
68
62
|
<SocialProof
|
|
69
|
-
|
|
63
|
+
class="mt-[var(--space-stack-lg)]"
|
|
70
64
|
text={t('hero.socialProof')}
|
|
71
65
|
avatars={[
|
|
72
66
|
'https://i.pravatar.cc/150?img=12',
|