yaxa-svelte 2.0.0 → 2.0.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/dist/components/charts/DonutChart.svelte +3 -3
- package/dist/components/layout/Header.svelte +66 -6
- package/dist/components/navigation/Tabs.svelte +2 -2
- package/dist/components/overlays/Modal.svelte +1 -1
- package/dist/components/overlays/Modal.svelte.d.ts +1 -1
- package/dist/components/theme/ThemeStudio.svelte +51 -24
- package/package.json +1 -1
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
/>
|
|
84
84
|
|
|
85
85
|
<!-- Segments -->
|
|
86
|
-
{#each segments as seg, idx}
|
|
86
|
+
{#each segments as seg, idx (seg.item.label)}
|
|
87
87
|
<circle
|
|
88
88
|
role="graphics-symbol"
|
|
89
89
|
aria-label={seg.item.label}
|
|
@@ -131,9 +131,9 @@
|
|
|
131
131
|
<!-- Legend -->
|
|
132
132
|
{#if showLegend && data.length > 0}
|
|
133
133
|
<div
|
|
134
|
-
class="mt-4 flex flex-wrap justify-center gap-x-
|
|
134
|
+
class="mt-4 flex flex-wrap justify-center gap-x-3 gap-y-1.5 text-[11px] text-neutral-600 sm:gap-x-4 sm:gap-y-2 sm:text-xs dark:text-neutral-300"
|
|
135
135
|
>
|
|
136
|
-
{#each segments as seg, idx}
|
|
136
|
+
{#each segments as seg, idx (seg.item.label)}
|
|
137
137
|
<button
|
|
138
138
|
type="button"
|
|
139
139
|
onmouseenter={() => (hoveredIdx = idx)}
|
|
@@ -80,8 +80,22 @@
|
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
82
|
<!-- Right Section: Search, Theme Toggle, Actions, Socials -->
|
|
83
|
-
<div class="flex items-center gap-2">
|
|
83
|
+
<div class="flex items-center gap-1.5 sm:gap-2">
|
|
84
84
|
{#if onOpenCommand}
|
|
85
|
+
<!-- Mobile Search Button -->
|
|
86
|
+
<Button
|
|
87
|
+
variant="ghost"
|
|
88
|
+
color="neutral"
|
|
89
|
+
square
|
|
90
|
+
size="sm"
|
|
91
|
+
class="sm:hidden"
|
|
92
|
+
onclick={onOpenCommand}
|
|
93
|
+
aria-label="Search documentation"
|
|
94
|
+
>
|
|
95
|
+
<Icon name="search" size="sm" />
|
|
96
|
+
</Button>
|
|
97
|
+
|
|
98
|
+
<!-- Desktop Search Pill -->
|
|
85
99
|
<button
|
|
86
100
|
onclick={onOpenCommand}
|
|
87
101
|
type="button"
|
|
@@ -110,7 +124,7 @@
|
|
|
110
124
|
{/if}
|
|
111
125
|
</Button>
|
|
112
126
|
|
|
113
|
-
<!-- GitHub Link -->
|
|
127
|
+
<!-- GitHub Link (Desktop) -->
|
|
114
128
|
{#if currentConfig.socials?.github}
|
|
115
129
|
<Button
|
|
116
130
|
href={currentConfig.socials.github}
|
|
@@ -118,13 +132,14 @@
|
|
|
118
132
|
color="neutral"
|
|
119
133
|
square
|
|
120
134
|
size="sm"
|
|
135
|
+
class="hidden sm:inline-flex"
|
|
121
136
|
aria-label="GitHub Repository"
|
|
122
137
|
>
|
|
123
138
|
<Icon name="github" size="sm" />
|
|
124
139
|
</Button>
|
|
125
140
|
{/if}
|
|
126
141
|
|
|
127
|
-
<!-- npm Link -->
|
|
142
|
+
<!-- npm Link (Desktop) -->
|
|
128
143
|
{#if currentConfig.socials?.npm}
|
|
129
144
|
<Button
|
|
130
145
|
href={currentConfig.socials.npm}
|
|
@@ -132,6 +147,7 @@
|
|
|
132
147
|
color="neutral"
|
|
133
148
|
square
|
|
134
149
|
size="sm"
|
|
150
|
+
class="hidden sm:inline-flex"
|
|
135
151
|
aria-label="npm Package"
|
|
136
152
|
>
|
|
137
153
|
<Icon name="npm" size="sm" />
|
|
@@ -173,17 +189,61 @@
|
|
|
173
189
|
<!-- Mobile Navigation Dropdown -->
|
|
174
190
|
{#if mobileMenuOpen}
|
|
175
191
|
<div class="border-t border-neutral-200 py-4 md:hidden dark:border-neutral-800">
|
|
176
|
-
<div class="flex flex-col space-y-
|
|
192
|
+
<div class="flex flex-col space-y-1">
|
|
177
193
|
{#each resolvedLinks as item (item.href)}
|
|
178
194
|
<a
|
|
179
195
|
href={item.href}
|
|
180
196
|
onclick={() => (mobileMenuOpen = false)}
|
|
181
|
-
class="rounded-lg px-3 py-2 text-base font-medium text-neutral-700 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800"
|
|
197
|
+
class="flex items-center justify-between rounded-lg px-3 py-2 text-base font-medium text-neutral-700 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800"
|
|
182
198
|
>
|
|
183
|
-
|
|
199
|
+
<span class="flex items-center gap-2">
|
|
200
|
+
{#if item.icon}
|
|
201
|
+
<Icon name={item.icon} size="xs" />
|
|
202
|
+
{/if}
|
|
203
|
+
{item.label}
|
|
204
|
+
</span>
|
|
205
|
+
{#if item.badge}
|
|
206
|
+
<span
|
|
207
|
+
class="rounded bg-primary-100 px-1.5 py-0.5 text-xs font-semibold text-primary-700 dark:bg-primary-950 dark:text-primary-300"
|
|
208
|
+
>
|
|
209
|
+
{item.badge}
|
|
210
|
+
</span>
|
|
211
|
+
{/if}
|
|
184
212
|
</a>
|
|
185
213
|
{/each}
|
|
186
214
|
</div>
|
|
215
|
+
|
|
216
|
+
<!-- Mobile Menu Socials -->
|
|
217
|
+
{#if currentConfig.socials?.github || currentConfig.socials?.npm}
|
|
218
|
+
<div
|
|
219
|
+
class="mt-4 flex items-center gap-2 border-t border-neutral-200 pt-4 dark:border-neutral-800"
|
|
220
|
+
>
|
|
221
|
+
{#if currentConfig.socials?.github}
|
|
222
|
+
<Button
|
|
223
|
+
href={currentConfig.socials.github}
|
|
224
|
+
variant="outline"
|
|
225
|
+
size="sm"
|
|
226
|
+
class="flex-1 justify-center"
|
|
227
|
+
aria-label="GitHub Repository"
|
|
228
|
+
>
|
|
229
|
+
<Icon name="github" size="xs" class="mr-1.5" />
|
|
230
|
+
GitHub
|
|
231
|
+
</Button>
|
|
232
|
+
{/if}
|
|
233
|
+
{#if currentConfig.socials?.npm}
|
|
234
|
+
<Button
|
|
235
|
+
href={currentConfig.socials.npm}
|
|
236
|
+
variant="outline"
|
|
237
|
+
size="sm"
|
|
238
|
+
class="flex-1 justify-center"
|
|
239
|
+
aria-label="npm Package"
|
|
240
|
+
>
|
|
241
|
+
<Icon name="npm" size="xs" class="mr-1.5" />
|
|
242
|
+
npm
|
|
243
|
+
</Button>
|
|
244
|
+
{/if}
|
|
245
|
+
</div>
|
|
246
|
+
{/if}
|
|
187
247
|
</div>
|
|
188
248
|
{/if}
|
|
189
249
|
</Container>
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
bind:this={listElement}
|
|
80
80
|
data-slot="list"
|
|
81
81
|
class={cn(
|
|
82
|
-
'relative flex',
|
|
82
|
+
'relative scrollbar-none flex max-w-full overflow-x-auto',
|
|
83
83
|
variant === 'segmented' &&
|
|
84
84
|
'rounded-xl border border-neutral-200/60 bg-neutral-100 p-1 dark:border-neutral-800/60 dark:bg-neutral-900',
|
|
85
85
|
variant === 'underline' && 'gap-6 border-b border-neutral-200 dark:border-neutral-800',
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
disabled={tab.disabled}
|
|
112
112
|
onclick={() => (value = tab.value)}
|
|
113
113
|
class={cn(
|
|
114
|
-
'relative z-10 inline-flex items-center justify-center gap-2 text-sm font-medium transition-all duration-150 select-none focus-visible:ring-2 focus-visible:ring-
|
|
114
|
+
'relative z-10 inline-flex shrink-0 items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-all duration-150 select-none focus-visible:ring-2 focus-visible:ring-(--yaxa-ring) focus-visible:outline-none motion-reduce:transition-none',
|
|
115
115
|
tab.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
|
|
116
116
|
variant === 'segmented' && [
|
|
117
117
|
'rounded-lg px-3 py-1.5',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
|
|
5
5
|
export const modalVariants = tv({
|
|
6
|
-
base: 'fixed left-1/2 top-1/2 z-50 w-
|
|
6
|
+
base: 'fixed left-1/2 top-1/2 z-50 w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-2xl border border-neutral-200 bg-white p-4 sm:p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 dark:border-neutral-800 dark:bg-neutral-900 yaxa-surface-elevated motion-reduce:transition-none',
|
|
7
7
|
variants: {
|
|
8
8
|
size: {
|
|
9
9
|
sm: 'max-w-sm',
|
|
@@ -8,7 +8,7 @@ export declare const modalVariants: import("tailwind-variants").TVReturnType<{
|
|
|
8
8
|
xl: "max-w-4xl";
|
|
9
9
|
full: "max-w-[calc(100vw-2rem)] h-[calc(100vh-2rem)]";
|
|
10
10
|
};
|
|
11
|
-
}, undefined, "fixed left-1/2 top-1/2 z-50 w-
|
|
11
|
+
}, undefined, "fixed left-1/2 top-1/2 z-50 w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-2xl border border-neutral-200 bg-white p-4 sm:p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 dark:border-neutral-800 dark:bg-neutral-900 yaxa-surface-elevated motion-reduce:transition-none", {
|
|
12
12
|
size: {
|
|
13
13
|
sm: "max-w-sm";
|
|
14
14
|
md: "max-w-lg";
|
|
@@ -91,20 +91,23 @@
|
|
|
91
91
|
{#if showHeader}
|
|
92
92
|
<!-- Top Bar -->
|
|
93
93
|
<div
|
|
94
|
-
class="flex flex-wrap items-center justify-between gap-4 rounded-2xl border border-neutral-200 bg-white p-
|
|
94
|
+
class="flex flex-wrap items-center justify-between gap-4 rounded-2xl border border-neutral-200 bg-white p-4 shadow-sm sm:p-6 dark:border-neutral-800 dark:bg-neutral-900"
|
|
95
95
|
>
|
|
96
|
-
<div>
|
|
97
|
-
<h1 class="text-
|
|
96
|
+
<div class="min-w-0 flex-1">
|
|
97
|
+
<h1 class="text-lg font-bold text-neutral-900 sm:text-xl dark:text-white">
|
|
98
|
+
Visual Web Theme Studio
|
|
99
|
+
</h1>
|
|
98
100
|
<p class="mt-1 text-xs text-neutral-500">
|
|
99
101
|
Visually customize palettes, harmonic radii, fonts, and scrollbars with instant live
|
|
100
102
|
preview and zero FOUC.
|
|
101
103
|
</p>
|
|
102
104
|
</div>
|
|
103
105
|
|
|
104
|
-
<div class="flex items-center gap-2
|
|
106
|
+
<div class="flex w-full flex-wrap items-center gap-2 sm:w-auto">
|
|
105
107
|
<Button
|
|
106
108
|
variant="outline"
|
|
107
109
|
size="sm"
|
|
110
|
+
class="flex-1 sm:flex-initial"
|
|
108
111
|
onclick={() => {
|
|
109
112
|
const chosen = theme.randomize();
|
|
110
113
|
toast.info(
|
|
@@ -117,22 +120,33 @@
|
|
|
117
120
|
Randomize
|
|
118
121
|
</Button>
|
|
119
122
|
|
|
120
|
-
<Button
|
|
123
|
+
<Button
|
|
124
|
+
variant="outline"
|
|
125
|
+
size="sm"
|
|
126
|
+
class="flex-1 sm:flex-initial"
|
|
127
|
+
onclick={() => theme.reset()}
|
|
128
|
+
>
|
|
121
129
|
<Icon name="rotate-ccw" size="xs" class="mr-1.5" />
|
|
122
130
|
Reset
|
|
123
131
|
</Button>
|
|
124
132
|
|
|
125
|
-
<Button
|
|
133
|
+
<Button
|
|
134
|
+
variant="solid"
|
|
135
|
+
size="sm"
|
|
136
|
+
class="w-full sm:w-auto"
|
|
137
|
+
onclick={() => (exportModalOpen = true)}
|
|
138
|
+
>
|
|
126
139
|
<Icon name="download" size="xs" class="mr-1.5" />
|
|
127
140
|
Export Theme Tokens
|
|
128
141
|
</Button>
|
|
129
142
|
</div>
|
|
130
143
|
</div>
|
|
131
144
|
{:else}
|
|
132
|
-
<div class="flex items-center justify-end gap-2
|
|
145
|
+
<div class="flex w-full flex-wrap items-center justify-end gap-2 sm:w-auto">
|
|
133
146
|
<Button
|
|
134
147
|
variant="outline"
|
|
135
148
|
size="sm"
|
|
149
|
+
class="flex-1 sm:flex-initial"
|
|
136
150
|
onclick={() => {
|
|
137
151
|
const chosen = theme.randomize();
|
|
138
152
|
toast.info(
|
|
@@ -145,12 +159,22 @@
|
|
|
145
159
|
Randomize
|
|
146
160
|
</Button>
|
|
147
161
|
|
|
148
|
-
<Button
|
|
162
|
+
<Button
|
|
163
|
+
variant="outline"
|
|
164
|
+
size="sm"
|
|
165
|
+
class="flex-1 sm:flex-initial"
|
|
166
|
+
onclick={() => theme.reset()}
|
|
167
|
+
>
|
|
149
168
|
<Icon name="rotate-ccw" size="xs" class="mr-1.5" />
|
|
150
169
|
Reset
|
|
151
170
|
</Button>
|
|
152
171
|
|
|
153
|
-
<Button
|
|
172
|
+
<Button
|
|
173
|
+
variant="solid"
|
|
174
|
+
size="sm"
|
|
175
|
+
class="w-full sm:w-auto"
|
|
176
|
+
onclick={() => (exportModalOpen = true)}
|
|
177
|
+
>
|
|
154
178
|
<Icon name="download" size="xs" class="mr-1.5" />
|
|
155
179
|
Export Theme Tokens
|
|
156
180
|
</Button>
|
|
@@ -167,12 +191,12 @@
|
|
|
167
191
|
Color Mode
|
|
168
192
|
</div>
|
|
169
193
|
<div class="grid grid-cols-3 gap-1.5">
|
|
170
|
-
{#each ['light', 'dark', 'system'] as const as mode}
|
|
194
|
+
{#each ['light', 'dark', 'system'] as const as mode (mode)}
|
|
171
195
|
<Button
|
|
172
196
|
variant={theme.mode === mode ? 'solid' : 'outline'}
|
|
173
197
|
size="xs"
|
|
174
198
|
onclick={() => theme.setMode(mode)}
|
|
175
|
-
class="w-full justify-center px-1.5 py-1 text-[11px] capitalize"
|
|
199
|
+
class="min-h-9 w-full justify-center px-1.5 py-1 text-[11px] capitalize"
|
|
176
200
|
>
|
|
177
201
|
<Icon
|
|
178
202
|
name={mode === 'light' ? 'sun' : mode === 'dark' ? 'moon' : 'laptop'}
|
|
@@ -199,7 +223,7 @@
|
|
|
199
223
|
</span>
|
|
200
224
|
</div>
|
|
201
225
|
<div class="grid grid-cols-4 gap-1.5">
|
|
202
|
-
{#each Object.entries(ACCENT_PALETTES) as [key, pal]}
|
|
226
|
+
{#each Object.entries(ACCENT_PALETTES) as [key, pal] (key)}
|
|
203
227
|
<button
|
|
204
228
|
type="button"
|
|
205
229
|
onclick={() => theme.setAccent(key as AccentName)}
|
|
@@ -234,7 +258,7 @@
|
|
|
234
258
|
</span>
|
|
235
259
|
</div>
|
|
236
260
|
<div class="grid grid-cols-2 gap-2">
|
|
237
|
-
{#each Object.entries(NEUTRAL_PALETTES) as [key, pal]}
|
|
261
|
+
{#each Object.entries(NEUTRAL_PALETTES) as [key, pal] (key)}
|
|
238
262
|
{@const active = theme.neutral === key}
|
|
239
263
|
<button
|
|
240
264
|
type="button"
|
|
@@ -284,12 +308,12 @@
|
|
|
284
308
|
<span class="text-xs font-semibold text-primary-500 capitalize">{theme.radius}</span>
|
|
285
309
|
</div>
|
|
286
310
|
<div class="grid grid-cols-5 gap-1">
|
|
287
|
-
{#each Object.entries(RADIUS_PRESETS) as [key, r]}
|
|
311
|
+
{#each Object.entries(RADIUS_PRESETS) as [key, r] (key)}
|
|
288
312
|
{@const active = theme.radius === key}
|
|
289
313
|
<button
|
|
290
314
|
type="button"
|
|
291
315
|
onclick={() => theme.setRadius(key as RadiusPreset)}
|
|
292
|
-
class="border px-1 py-1.5 text-center text-[10px] font-semibold transition-all {active
|
|
316
|
+
class="min-h-9 min-w-0 border px-1 py-1.5 text-center text-[10px] font-semibold transition-all sm:text-xs {active
|
|
293
317
|
? 'border-primary-500 bg-primary-500 font-bold text-white shadow-xs'
|
|
294
318
|
: 'border-neutral-200 bg-white text-neutral-700 hover:bg-neutral-100 dark:border-neutral-800 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700'}"
|
|
295
319
|
style="border-radius: {r.value} !important;"
|
|
@@ -306,12 +330,12 @@
|
|
|
306
330
|
Typography & Sizing
|
|
307
331
|
</div>
|
|
308
332
|
<div class="grid grid-cols-3 gap-1">
|
|
309
|
-
{#each Object.entries(FONT_PRESETS) as [key, font]}
|
|
333
|
+
{#each Object.entries(FONT_PRESETS) as [key, font] (key)}
|
|
310
334
|
{@const active = theme.fontFamily === key}
|
|
311
335
|
<button
|
|
312
336
|
type="button"
|
|
313
337
|
onclick={() => theme.setFontFamily(key as FontFamily)}
|
|
314
|
-
class="rounded-md border px-1 py-1.5 text-center text-[11px] font-semibold capitalize transition-all {active
|
|
338
|
+
class="min-h-9 rounded-md border px-1 py-1.5 text-center text-[11px] font-semibold capitalize transition-all {active
|
|
315
339
|
? 'border-primary-500 bg-primary-500 text-white shadow-xs'
|
|
316
340
|
: 'border-neutral-200 bg-white text-neutral-700 hover:bg-neutral-100 dark:border-neutral-800 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700'}"
|
|
317
341
|
style="font-family: {font.value} !important;"
|
|
@@ -326,12 +350,12 @@
|
|
|
326
350
|
Density / Size Scale
|
|
327
351
|
</div>
|
|
328
352
|
<div class="grid grid-cols-3 gap-1">
|
|
329
|
-
{#each Object.entries(FONT_SIZE_PRESETS) as [key, sz]}
|
|
353
|
+
{#each Object.entries(FONT_SIZE_PRESETS) as [key, sz] (key)}
|
|
330
354
|
<Button
|
|
331
355
|
variant={theme.fontSize === key ? 'solid' : 'outline'}
|
|
332
356
|
size="xs"
|
|
333
357
|
onclick={() => theme.setFontSize(key as BaseFontSize)}
|
|
334
|
-
class="w-full justify-center px-
|
|
358
|
+
class="min-h-8.5 w-full min-w-0 justify-center px-0.5 py-1 text-[10px] sm:px-1 sm:text-[11px]"
|
|
335
359
|
>
|
|
336
360
|
<span class="truncate">{sz.name}</span>
|
|
337
361
|
</Button>
|
|
@@ -342,14 +366,17 @@
|
|
|
342
366
|
|
|
343
367
|
<!-- Scrollbar Auto-Alignment -->
|
|
344
368
|
<Card padding="sm" class="space-y-2">
|
|
345
|
-
<div class="flex items-center justify-between">
|
|
346
|
-
<div>
|
|
369
|
+
<div class="flex items-center justify-between gap-3">
|
|
370
|
+
<div class="min-w-0 flex-1">
|
|
347
371
|
<div class="text-xs font-bold text-neutral-900 dark:text-white">Accent Scrollbars</div>
|
|
348
|
-
<div class="text-[11px] text-neutral-500">
|
|
372
|
+
<div class="truncate text-[11px] text-neutral-500 sm:whitespace-normal">
|
|
373
|
+
Sync scrollbar thumb to active accent
|
|
374
|
+
</div>
|
|
349
375
|
</div>
|
|
350
376
|
<Button
|
|
351
377
|
variant={theme.accentScrollbar ? 'solid' : 'outline'}
|
|
352
378
|
size="xs"
|
|
379
|
+
class="shrink-0"
|
|
353
380
|
onclick={() => theme.setAccentScrollbar(!theme.accentScrollbar)}
|
|
354
381
|
>
|
|
355
382
|
{theme.accentScrollbar ? 'Enabled' : 'Disabled'}
|
|
@@ -361,10 +388,10 @@
|
|
|
361
388
|
<!-- Right Live Sandbox Canvas (7 cols on xl, 8 cols on 2xl) -->
|
|
362
389
|
<div class="space-y-6 xl:col-span-7 2xl:col-span-8">
|
|
363
390
|
<!-- Elements Preview Card -->
|
|
364
|
-
<Card>
|
|
391
|
+
<Card ui={{ header: 'p-4 sm:p-6', body: 'p-4 sm:p-6 space-y-6' }}>
|
|
365
392
|
{#snippet header()}
|
|
366
393
|
<div class="flex items-center justify-between">
|
|
367
|
-
<span class="font-semibold text-neutral-900 dark:text-white">
|
|
394
|
+
<span class="text-sm font-semibold text-neutral-900 sm:text-base dark:text-white">
|
|
368
395
|
Interactive Component Sandbox
|
|
369
396
|
</span>
|
|
370
397
|
<Badge variant="soft" color="primary">Live Preview</Badge>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaxa-svelte",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "The Intuitive Svelte UI Library — Nuxt UI v4 equivalent for SvelteKit 2 & 3 & Svelte 5 with automated SEO, dynamic OpenGraph cards, sitemaps, robots.txt, and Tailwind CSS v4.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://yaxa.vercel.app",
|