@autumnsgrove/groveengine 0.8.0 → 0.8.6
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/OnboardingChecklist.svelte +2 -2
- package/dist/components/WispButton.svelte +83 -0
- package/dist/components/WispButton.svelte.d.ts +49 -0
- package/dist/components/WispPanel.svelte +1092 -0
- package/dist/components/WispPanel.svelte.d.ts +49 -0
- package/dist/components/custom/ContentWithGutter.svelte +7 -13
- package/dist/components/custom/TableOfContents.svelte +12 -1
- package/dist/components/quota/UpgradePrompt.svelte +1 -0
- package/dist/config/wisp.d.ts +145 -0
- package/dist/config/wisp.js +175 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/server/inference-client.d.ts +139 -0
- package/dist/server/inference-client.js +294 -0
- package/dist/ui/components/forms/SearchInput.svelte +0 -1
- package/dist/ui/components/gallery/ImageGallery.svelte +14 -3
- package/dist/ui/components/gallery/Lightbox.svelte +8 -3
- package/dist/ui/components/gallery/ZoomableImage.svelte +12 -2
- package/dist/ui/components/nature/Logo.svelte +55 -19
- package/dist/ui/components/nature/botanical/LeafFalling.svelte +2 -2
- package/dist/ui/components/nature/botanical/PetalFalling.svelte +7 -7
- package/dist/ui/components/nature/ground/Crocus.svelte +3 -3
- package/dist/ui/components/nature/ground/Daffodil.svelte +3 -3
- package/dist/ui/components/nature/ground/Tulip.svelte +5 -5
- package/dist/ui/components/nature/palette.d.ts +187 -76
- package/dist/ui/components/nature/palette.js +169 -81
- package/dist/ui/components/nature/trees/TreeCherry.svelte +3 -3
- package/dist/ui/components/nature/trees/TreeCherry.svelte.d.ts +1 -1
- package/dist/ui/components/nature/trees/TreePine.svelte +2 -2
- package/dist/ui/components/nature/trees/TreePine.svelte.d.ts +1 -1
- package/dist/ui/components/primitives/textarea/textarea.svelte +1 -1
- package/dist/ui/components/typography/Alagard.svelte +17 -0
- package/dist/ui/components/typography/Alagard.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Atkinson.svelte +17 -0
- package/dist/ui/components/typography/Atkinson.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Calistoga.svelte +17 -0
- package/dist/ui/components/typography/Calistoga.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Caveat.svelte +17 -0
- package/dist/ui/components/typography/Caveat.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Cozette.svelte +17 -0
- package/dist/ui/components/typography/Cozette.svelte.d.ts +10 -0
- package/dist/ui/components/typography/FontProvider.svelte +98 -0
- package/dist/ui/components/typography/FontProvider.svelte.d.ts +17 -0
- package/dist/ui/components/typography/IBMPlexMono.svelte +17 -0
- package/dist/ui/components/typography/IBMPlexMono.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Lexend.svelte +17 -0
- package/dist/ui/components/typography/Lexend.svelte.d.ts +10 -0
- package/dist/ui/components/typography/OpenDyslexic.svelte +17 -0
- package/dist/ui/components/typography/OpenDyslexic.svelte.d.ts +10 -0
- package/dist/ui/components/typography/PlusJakartaSans.svelte +17 -0
- package/dist/ui/components/typography/PlusJakartaSans.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Quicksand.svelte +17 -0
- package/dist/ui/components/typography/Quicksand.svelte.d.ts +10 -0
- package/dist/ui/components/typography/README.md +153 -0
- package/dist/ui/components/typography/index.d.ts +13 -0
- package/dist/ui/components/typography/index.js +31 -0
- package/dist/ui/components/ui/CollapsibleSection.svelte +10 -0
- package/dist/ui/components/ui/GlassCarousel.svelte +446 -0
- package/dist/ui/components/ui/GlassCarousel.svelte.d.ts +57 -0
- package/dist/ui/components/ui/GlassConfirmDialog.svelte +2 -1
- package/dist/ui/components/ui/GlassLogo.svelte +2 -1
- package/dist/ui/components/ui/GlassOverlay.svelte +1 -1
- package/dist/ui/components/ui/index.d.ts +1 -0
- package/dist/ui/components/ui/index.js +1 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/tokens/fonts.d.ts +1 -1
- package/dist/ui/tokens/fonts.js +0 -126
- package/dist/ui/vineyard/index.d.ts +9 -0
- package/dist/ui/vineyard/index.js +8 -0
- package/dist/utils/csrf.js +5 -2
- package/dist/utils/readability.d.ts +89 -0
- package/dist/utils/readability.js +204 -0
- package/package.json +38 -21
- package/static/fonts/alagard.ttf +0 -0
- package/LICENSE +0 -378
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GlassCarousel - A stack-style carousel with glassmorphism styling
|
|
8
|
+
*
|
|
9
|
+
* Mobile-first carousel where cards stack on top of each other.
|
|
10
|
+
* Supports images out of the box, or custom content via children snippet.
|
|
11
|
+
*
|
|
12
|
+
* Navigation: touch/swipe, mouse drag, click (arrows/dots), keyboard (arrow keys).
|
|
13
|
+
*
|
|
14
|
+
* @example Image carousel
|
|
15
|
+
* ```svelte
|
|
16
|
+
* <GlassCarousel images={[
|
|
17
|
+
* { url: '/photo1.jpg', alt: 'Beach sunset', caption: 'Summer vibes' },
|
|
18
|
+
* { url: '/photo2.jpg', alt: 'Mountain view' }
|
|
19
|
+
* ]} />
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example Custom content carousel
|
|
23
|
+
* ```svelte
|
|
24
|
+
* <GlassCarousel itemCount={3} let:index>
|
|
25
|
+
* {#snippet item(index)}
|
|
26
|
+
* <TestimonialCard data={testimonials[index]} />
|
|
27
|
+
* {/snippet}
|
|
28
|
+
* </GlassCarousel>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
interface CarouselImage {
|
|
33
|
+
url: string;
|
|
34
|
+
alt: string;
|
|
35
|
+
caption?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Custom content renderer type - receives slide index */
|
|
39
|
+
type ItemRenderer = Snippet<[number]>;
|
|
40
|
+
|
|
41
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
|
|
42
|
+
/** Array of images to display */
|
|
43
|
+
images?: CarouselImage[];
|
|
44
|
+
/** Number of items when using custom content (ignored if images provided) */
|
|
45
|
+
itemCount?: number;
|
|
46
|
+
/** Show navigation dots */
|
|
47
|
+
showDots?: boolean;
|
|
48
|
+
/** Show arrow buttons */
|
|
49
|
+
showArrows?: boolean;
|
|
50
|
+
/** Enable autoplay (disabled by default) */
|
|
51
|
+
autoplay?: boolean;
|
|
52
|
+
/** Autoplay interval in milliseconds */
|
|
53
|
+
autoplayInterval?: number;
|
|
54
|
+
/** Visual variant */
|
|
55
|
+
variant?: "default" | "frosted" | "minimal";
|
|
56
|
+
/** Custom class name */
|
|
57
|
+
class?: string;
|
|
58
|
+
/** Custom content renderer - receives index */
|
|
59
|
+
item?: ItemRenderer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let {
|
|
63
|
+
images = [],
|
|
64
|
+
itemCount = 0,
|
|
65
|
+
showDots = true,
|
|
66
|
+
showArrows = true,
|
|
67
|
+
autoplay = false,
|
|
68
|
+
autoplayInterval = 5000,
|
|
69
|
+
variant = "default",
|
|
70
|
+
class: className,
|
|
71
|
+
item,
|
|
72
|
+
...restProps
|
|
73
|
+
}: Props = $props();
|
|
74
|
+
|
|
75
|
+
// Determine total count from images or itemCount
|
|
76
|
+
let totalItems = $derived(images.length > 0 ? images.length : itemCount);
|
|
77
|
+
|
|
78
|
+
// Current slide state
|
|
79
|
+
let currentIndex = $state(0);
|
|
80
|
+
let isAnimating = $state(false);
|
|
81
|
+
|
|
82
|
+
// Touch/drag state
|
|
83
|
+
let touchStartX = $state(0);
|
|
84
|
+
let touchCurrentX = $state(0);
|
|
85
|
+
let isDragging = $state(false);
|
|
86
|
+
let dragOffset = $state(0);
|
|
87
|
+
|
|
88
|
+
// Autoplay interval reference
|
|
89
|
+
let autoplayTimer: ReturnType<typeof setInterval> | null = null;
|
|
90
|
+
|
|
91
|
+
// Configuration constants
|
|
92
|
+
const ANIMATION_DURATION_MS = 400;
|
|
93
|
+
const SWIPE_THRESHOLD_PX = 50;
|
|
94
|
+
const DRAG_INFLUENCE_FACTOR = 0.3;
|
|
95
|
+
const MIN_CARD_SCALE = 0.85;
|
|
96
|
+
const SCALE_STEP = 0.05;
|
|
97
|
+
const CARD_OFFSET_X = 20;
|
|
98
|
+
const CARD_OFFSET_Y = 8;
|
|
99
|
+
const MIN_OPACITY = 0.4;
|
|
100
|
+
const OPACITY_STEP = 0.3;
|
|
101
|
+
|
|
102
|
+
function goTo(index: number) {
|
|
103
|
+
if (isAnimating || index < 0 || index >= totalItems || index === currentIndex) return;
|
|
104
|
+
|
|
105
|
+
isAnimating = true;
|
|
106
|
+
currentIndex = index;
|
|
107
|
+
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
isAnimating = false;
|
|
110
|
+
}, ANIMATION_DURATION_MS);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function goNext() {
|
|
114
|
+
if (currentIndex < totalItems - 1) {
|
|
115
|
+
goTo(currentIndex + 1);
|
|
116
|
+
} else {
|
|
117
|
+
// Loop back to start
|
|
118
|
+
goTo(0);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function goPrev() {
|
|
123
|
+
if (currentIndex > 0) {
|
|
124
|
+
goTo(currentIndex - 1);
|
|
125
|
+
} else {
|
|
126
|
+
// Loop to end
|
|
127
|
+
goTo(totalItems - 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Touch handlers
|
|
132
|
+
function handleTouchStart(event: TouchEvent) {
|
|
133
|
+
if (isAnimating) return;
|
|
134
|
+
touchStartX = event.touches[0].clientX;
|
|
135
|
+
touchCurrentX = touchStartX;
|
|
136
|
+
isDragging = true;
|
|
137
|
+
stopAutoplay();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function handleTouchMove(event: TouchEvent) {
|
|
141
|
+
if (!isDragging) return;
|
|
142
|
+
touchCurrentX = event.touches[0].clientX;
|
|
143
|
+
dragOffset = touchCurrentX - touchStartX;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function handleTouchEnd() {
|
|
147
|
+
if (!isDragging) return;
|
|
148
|
+
|
|
149
|
+
const diff = touchStartX - touchCurrentX;
|
|
150
|
+
|
|
151
|
+
if (Math.abs(diff) > SWIPE_THRESHOLD_PX) {
|
|
152
|
+
if (diff > 0) {
|
|
153
|
+
goNext();
|
|
154
|
+
} else {
|
|
155
|
+
goPrev();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
isDragging = false;
|
|
160
|
+
dragOffset = 0;
|
|
161
|
+
touchStartX = 0;
|
|
162
|
+
touchCurrentX = 0;
|
|
163
|
+
|
|
164
|
+
if (autoplay) startAutoplay();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Mouse drag handlers (for desktop)
|
|
168
|
+
function handleMouseDown(event: MouseEvent) {
|
|
169
|
+
if (isAnimating) return;
|
|
170
|
+
touchStartX = event.clientX;
|
|
171
|
+
touchCurrentX = touchStartX;
|
|
172
|
+
isDragging = true;
|
|
173
|
+
stopAutoplay();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function handleMouseMove(event: MouseEvent) {
|
|
177
|
+
if (!isDragging) return;
|
|
178
|
+
touchCurrentX = event.clientX;
|
|
179
|
+
dragOffset = touchCurrentX - touchStartX;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function handleMouseUp() {
|
|
183
|
+
handleTouchEnd();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function handleMouseLeave() {
|
|
187
|
+
if (isDragging) {
|
|
188
|
+
handleTouchEnd();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Keyboard navigation
|
|
193
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
194
|
+
switch (event.key) {
|
|
195
|
+
case 'ArrowLeft':
|
|
196
|
+
event.preventDefault();
|
|
197
|
+
goPrev();
|
|
198
|
+
break;
|
|
199
|
+
case 'ArrowRight':
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
goNext();
|
|
202
|
+
break;
|
|
203
|
+
case 'Home':
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
goTo(0);
|
|
206
|
+
break;
|
|
207
|
+
case 'End':
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
goTo(totalItems - 1);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Autoplay controls
|
|
215
|
+
function startAutoplay() {
|
|
216
|
+
if (!autoplay || totalItems <= 1) return;
|
|
217
|
+
stopAutoplay();
|
|
218
|
+
autoplayTimer = setInterval(goNext, autoplayInterval);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function stopAutoplay() {
|
|
222
|
+
if (autoplayTimer) {
|
|
223
|
+
clearInterval(autoplayTimer);
|
|
224
|
+
autoplayTimer = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Reset drag state helper
|
|
229
|
+
function resetDragState() {
|
|
230
|
+
isDragging = false;
|
|
231
|
+
dragOffset = 0;
|
|
232
|
+
touchStartX = 0;
|
|
233
|
+
touchCurrentX = 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Start/stop autoplay based on prop, with cleanup for drag state
|
|
237
|
+
$effect(() => {
|
|
238
|
+
if (autoplay && totalItems > 1) {
|
|
239
|
+
startAutoplay();
|
|
240
|
+
} else {
|
|
241
|
+
stopAutoplay();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return () => {
|
|
245
|
+
stopAutoplay();
|
|
246
|
+
// Clean up drag state if component unmounts during drag
|
|
247
|
+
if (isDragging) {
|
|
248
|
+
resetDragState();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Handle window-level mouseup for edge case: drag starts, mouse leaves window, released outside
|
|
254
|
+
$effect(() => {
|
|
255
|
+
if (!isDragging) return;
|
|
256
|
+
|
|
257
|
+
const handleWindowMouseUp = () => handleMouseUp();
|
|
258
|
+
window.addEventListener('mouseup', handleWindowMouseUp);
|
|
259
|
+
|
|
260
|
+
return () => window.removeEventListener('mouseup', handleWindowMouseUp);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Reset currentIndex if totalItems changes and current index is out of bounds
|
|
264
|
+
$effect(() => {
|
|
265
|
+
if (totalItems > 0 && currentIndex >= totalItems) {
|
|
266
|
+
currentIndex = totalItems - 1;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Calculate card transforms for stack effect
|
|
271
|
+
function getCardStyle(index: number): string {
|
|
272
|
+
const offset = index - currentIndex;
|
|
273
|
+
const dragInfluence = isDragging ? dragOffset * DRAG_INFLUENCE_FACTOR : 0;
|
|
274
|
+
|
|
275
|
+
// Current card - special case
|
|
276
|
+
if (offset === 0) {
|
|
277
|
+
const translateX = isDragging ? dragOffset : 0;
|
|
278
|
+
return `
|
|
279
|
+
transform: translateX(${translateX}px) scale(1);
|
|
280
|
+
opacity: 1;
|
|
281
|
+
z-index: ${totalItems + 1};
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Cards behind or ahead - shared transform logic
|
|
286
|
+
const depth = Math.abs(offset);
|
|
287
|
+
const direction = offset < 0 ? -1 : 1;
|
|
288
|
+
const scale = Math.max(MIN_CARD_SCALE, 1 - depth * SCALE_STEP);
|
|
289
|
+
const translateX = direction * CARD_OFFSET_X * depth + dragInfluence;
|
|
290
|
+
const translateY = CARD_OFFSET_Y * depth;
|
|
291
|
+
const opacity = Math.max(MIN_OPACITY, 1 - depth * OPACITY_STEP);
|
|
292
|
+
const zIndex = totalItems - depth;
|
|
293
|
+
|
|
294
|
+
return `
|
|
295
|
+
transform: translateX(${translateX}px) translateY(${translateY}px) scale(${scale});
|
|
296
|
+
opacity: ${opacity};
|
|
297
|
+
z-index: ${zIndex};
|
|
298
|
+
`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Variant styles
|
|
302
|
+
const variantClasses = {
|
|
303
|
+
default: "bg-white/60 dark:bg-emerald-950/25 backdrop-blur-md border-white/40 dark:border-emerald-800/25",
|
|
304
|
+
frosted: "bg-white/70 dark:bg-emerald-950/35 backdrop-blur-lg border-white/50 dark:border-emerald-800/30",
|
|
305
|
+
minimal: "bg-transparent border-transparent"
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const containerClass = $derived(
|
|
309
|
+
cn(
|
|
310
|
+
"relative overflow-hidden rounded-2xl border p-4 outline-none",
|
|
311
|
+
"focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2",
|
|
312
|
+
variant !== "minimal" && variantClasses[variant],
|
|
313
|
+
variant === "minimal" && "p-0",
|
|
314
|
+
className
|
|
315
|
+
)
|
|
316
|
+
);
|
|
317
|
+
</script>
|
|
318
|
+
|
|
319
|
+
{#if totalItems > 0}
|
|
320
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
321
|
+
<div
|
|
322
|
+
class={containerClass}
|
|
323
|
+
role="region"
|
|
324
|
+
aria-label="Image carousel"
|
|
325
|
+
aria-roledescription="carousel"
|
|
326
|
+
tabindex="0"
|
|
327
|
+
onkeydown={handleKeydown}
|
|
328
|
+
{...restProps}
|
|
329
|
+
>
|
|
330
|
+
<!-- Cards stack -->
|
|
331
|
+
<div
|
|
332
|
+
class="relative w-full aspect-[4/3] select-none"
|
|
333
|
+
ontouchstart={handleTouchStart}
|
|
334
|
+
ontouchmove={handleTouchMove}
|
|
335
|
+
ontouchend={handleTouchEnd}
|
|
336
|
+
onmousedown={handleMouseDown}
|
|
337
|
+
onmousemove={handleMouseMove}
|
|
338
|
+
onmouseup={handleMouseUp}
|
|
339
|
+
onmouseleave={handleMouseLeave}
|
|
340
|
+
role="presentation"
|
|
341
|
+
>
|
|
342
|
+
{#each { length: totalItems } as _, index (index)}
|
|
343
|
+
<div
|
|
344
|
+
class={cn(
|
|
345
|
+
"absolute inset-0 rounded-xl overflow-hidden shadow-lg transition-all duration-[400ms] ease-out",
|
|
346
|
+
"bg-white dark:bg-slate-900",
|
|
347
|
+
isDragging && "transition-none"
|
|
348
|
+
)}
|
|
349
|
+
style={getCardStyle(index)}
|
|
350
|
+
aria-hidden={index !== currentIndex}
|
|
351
|
+
>
|
|
352
|
+
{#if images.length > 0}
|
|
353
|
+
<!-- Image mode -->
|
|
354
|
+
<img
|
|
355
|
+
src={images[index].url}
|
|
356
|
+
alt={images[index].alt}
|
|
357
|
+
class="w-full h-full object-cover"
|
|
358
|
+
draggable="false"
|
|
359
|
+
/>
|
|
360
|
+
{#if images[index].caption}
|
|
361
|
+
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70 to-transparent">
|
|
362
|
+
<p class="text-white text-sm font-medium">{images[index].caption}</p>
|
|
363
|
+
</div>
|
|
364
|
+
{/if}
|
|
365
|
+
{:else if item}
|
|
366
|
+
<!-- Custom content mode -->
|
|
367
|
+
{@render item(index)}
|
|
368
|
+
{/if}
|
|
369
|
+
</div>
|
|
370
|
+
{/each}
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<!-- Navigation -->
|
|
374
|
+
{#if totalItems > 1}
|
|
375
|
+
<div class="flex items-center justify-between mt-4 px-2">
|
|
376
|
+
<!-- Previous arrow -->
|
|
377
|
+
{#if showArrows}
|
|
378
|
+
<button
|
|
379
|
+
type="button"
|
|
380
|
+
class={cn(
|
|
381
|
+
"w-10 h-10 rounded-full flex items-center justify-center",
|
|
382
|
+
"bg-white/60 dark:bg-emerald-950/40 backdrop-blur-sm",
|
|
383
|
+
"border border-white/40 dark:border-emerald-800/30",
|
|
384
|
+
"text-slate-700 dark:text-slate-200",
|
|
385
|
+
"hover:bg-white/80 dark:hover:bg-emerald-950/60",
|
|
386
|
+
"transition-all duration-200",
|
|
387
|
+
"disabled:opacity-40 disabled:cursor-not-allowed"
|
|
388
|
+
)}
|
|
389
|
+
onclick={goPrev}
|
|
390
|
+
aria-label="Previous slide"
|
|
391
|
+
>
|
|
392
|
+
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
393
|
+
<polyline points="15 18 9 12 15 6"></polyline>
|
|
394
|
+
</svg>
|
|
395
|
+
</button>
|
|
396
|
+
{:else}
|
|
397
|
+
<div></div>
|
|
398
|
+
{/if}
|
|
399
|
+
|
|
400
|
+
<!-- Dots -->
|
|
401
|
+
{#if showDots}
|
|
402
|
+
<div class="flex items-center gap-2">
|
|
403
|
+
{#each { length: totalItems } as _, index (index)}
|
|
404
|
+
<button
|
|
405
|
+
type="button"
|
|
406
|
+
class={cn(
|
|
407
|
+
"h-2 rounded-full transition-all duration-300",
|
|
408
|
+
index === currentIndex
|
|
409
|
+
? "w-6 bg-emerald-600 dark:bg-emerald-400"
|
|
410
|
+
: "w-2 bg-slate-300 dark:bg-slate-600 hover:bg-slate-400 dark:hover:bg-slate-500"
|
|
411
|
+
)}
|
|
412
|
+
onclick={() => goTo(index)}
|
|
413
|
+
aria-label={`Go to slide ${index + 1}`}
|
|
414
|
+
aria-current={index === currentIndex ? "true" : undefined}
|
|
415
|
+
></button>
|
|
416
|
+
{/each}
|
|
417
|
+
</div>
|
|
418
|
+
{/if}
|
|
419
|
+
|
|
420
|
+
<!-- Next arrow -->
|
|
421
|
+
{#if showArrows}
|
|
422
|
+
<button
|
|
423
|
+
type="button"
|
|
424
|
+
class={cn(
|
|
425
|
+
"w-10 h-10 rounded-full flex items-center justify-center",
|
|
426
|
+
"bg-white/60 dark:bg-emerald-950/40 backdrop-blur-sm",
|
|
427
|
+
"border border-white/40 dark:border-emerald-800/30",
|
|
428
|
+
"text-slate-700 dark:text-slate-200",
|
|
429
|
+
"hover:bg-white/80 dark:hover:bg-emerald-950/60",
|
|
430
|
+
"transition-all duration-200",
|
|
431
|
+
"disabled:opacity-40 disabled:cursor-not-allowed"
|
|
432
|
+
)}
|
|
433
|
+
onclick={goNext}
|
|
434
|
+
aria-label="Next slide"
|
|
435
|
+
>
|
|
436
|
+
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
437
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
438
|
+
</svg>
|
|
439
|
+
</button>
|
|
440
|
+
{:else}
|
|
441
|
+
<div></div>
|
|
442
|
+
{/if}
|
|
443
|
+
</div>
|
|
444
|
+
{/if}
|
|
445
|
+
</div>
|
|
446
|
+
{/if}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* GlassCarousel - A stack-style carousel with glassmorphism styling
|
|
5
|
+
*
|
|
6
|
+
* Mobile-first carousel where cards stack on top of each other.
|
|
7
|
+
* Supports images out of the box, or custom content via children snippet.
|
|
8
|
+
*
|
|
9
|
+
* Navigation: touch/swipe, mouse drag, click (arrows/dots), keyboard (arrow keys).
|
|
10
|
+
*
|
|
11
|
+
* @example Image carousel
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <GlassCarousel images={[
|
|
14
|
+
* { url: '/photo1.jpg', alt: 'Beach sunset', caption: 'Summer vibes' },
|
|
15
|
+
* { url: '/photo2.jpg', alt: 'Mountain view' }
|
|
16
|
+
* ]} />
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example Custom content carousel
|
|
20
|
+
* ```svelte
|
|
21
|
+
* <GlassCarousel itemCount={3} let:index>
|
|
22
|
+
* {#snippet item(index)}
|
|
23
|
+
* <TestimonialCard data={testimonials[index]} />
|
|
24
|
+
* {/snippet}
|
|
25
|
+
* </GlassCarousel>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
interface CarouselImage {
|
|
29
|
+
url: string;
|
|
30
|
+
alt: string;
|
|
31
|
+
caption?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Custom content renderer type - receives slide index */
|
|
34
|
+
type ItemRenderer = Snippet<[number]>;
|
|
35
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
|
|
36
|
+
/** Array of images to display */
|
|
37
|
+
images?: CarouselImage[];
|
|
38
|
+
/** Number of items when using custom content (ignored if images provided) */
|
|
39
|
+
itemCount?: number;
|
|
40
|
+
/** Show navigation dots */
|
|
41
|
+
showDots?: boolean;
|
|
42
|
+
/** Show arrow buttons */
|
|
43
|
+
showArrows?: boolean;
|
|
44
|
+
/** Enable autoplay (disabled by default) */
|
|
45
|
+
autoplay?: boolean;
|
|
46
|
+
/** Autoplay interval in milliseconds */
|
|
47
|
+
autoplayInterval?: number;
|
|
48
|
+
/** Visual variant */
|
|
49
|
+
variant?: "default" | "frosted" | "minimal";
|
|
50
|
+
/** Custom class name */
|
|
51
|
+
class?: string;
|
|
52
|
+
/** Custom content renderer - receives index */
|
|
53
|
+
item?: ItemRenderer;
|
|
54
|
+
}
|
|
55
|
+
declare const GlassCarousel: import("svelte").Component<Props, {}, "">;
|
|
56
|
+
type GlassCarousel = ReturnType<typeof GlassCarousel>;
|
|
57
|
+
export default GlassCarousel;
|
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
role="dialog"
|
|
130
130
|
aria-modal="true"
|
|
131
131
|
aria-labelledby="confirm-dialog-title"
|
|
132
|
+
tabindex="0"
|
|
132
133
|
transition:fade={{ duration: 150 }}
|
|
133
134
|
>
|
|
134
135
|
<!-- Dark overlay with blur -->
|
|
@@ -157,7 +158,7 @@
|
|
|
157
158
|
variant === "warning" && "bg-amber-100 dark:bg-amber-900/30",
|
|
158
159
|
variant === "default" && "bg-accent/10 dark:bg-accent/20"
|
|
159
160
|
)}>
|
|
160
|
-
<
|
|
161
|
+
<config.icon class={cn("w-6 h-6", config.iconClass)} />
|
|
161
162
|
</div>
|
|
162
163
|
<div class="flex-1 min-w-0">
|
|
163
164
|
<h3
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
const isWinter = $derived(season === 'winter');
|
|
73
73
|
|
|
74
74
|
// Generate unique ID for SVG filters to avoid conflicts when multiple logos exist
|
|
75
|
-
const
|
|
75
|
+
const randomId = `glass-logo-${Math.random().toString(36).slice(2, 9)}`;
|
|
76
|
+
const uniqueId = $derived(filterId ?? randomId);
|
|
76
77
|
|
|
77
78
|
// Breathing speed presets
|
|
78
79
|
const BREATHING_SPEEDS = {
|
|
@@ -22,6 +22,7 @@ export { default as GlassConfirmDialog } from './GlassConfirmDialog.svelte';
|
|
|
22
22
|
export { default as GlassNavbar } from './GlassNavbar.svelte';
|
|
23
23
|
export { default as GlassOverlay } from './GlassOverlay.svelte';
|
|
24
24
|
export { default as GlassLogo } from './GlassLogo.svelte';
|
|
25
|
+
export { default as GlassCarousel } from './GlassCarousel.svelte';
|
|
25
26
|
export { TableHeader, TableBody, TableRow, TableCell, TableHead, TableFooter, TableCaption, } from '../primitives/table';
|
|
26
27
|
export * from './toast.js';
|
|
27
28
|
export declare const UI_VERSION = "0.2.0";
|
|
@@ -31,6 +31,7 @@ export { default as GlassConfirmDialog } from './GlassConfirmDialog.svelte';
|
|
|
31
31
|
export { default as GlassNavbar } from './GlassNavbar.svelte';
|
|
32
32
|
export { default as GlassOverlay } from './GlassOverlay.svelte';
|
|
33
33
|
export { default as GlassLogo } from './GlassLogo.svelte';
|
|
34
|
+
export { default as GlassCarousel } from './GlassCarousel.svelte';
|
|
34
35
|
// Table sub-components (from primitives)
|
|
35
36
|
export { TableHeader, TableBody, TableRow, TableCell, TableHead, TableFooter, TableCaption, } from '../primitives/table';
|
|
36
37
|
// Toast utility
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export * from './components/forms/index.js';
|
|
|
6
6
|
export * from './components/icons/index.js';
|
|
7
7
|
export * from './components/states/index.js';
|
|
8
8
|
export * from './components/charts/index.js';
|
|
9
|
+
export * from './components/typography/index.js';
|
|
9
10
|
export * from './tokens/index.js';
|
|
10
11
|
export { cn } from './utils/cn.js';
|
package/dist/ui/index.js
CHANGED
|
@@ -16,6 +16,8 @@ export * from './components/icons/index.js';
|
|
|
16
16
|
export * from './components/states/index.js';
|
|
17
17
|
// Charts (from AutumnsGrove)
|
|
18
18
|
export * from './components/charts/index.js';
|
|
19
|
+
// Typography (font wrapper components)
|
|
20
|
+
export * from './components/typography/index.js';
|
|
19
21
|
// Export design tokens
|
|
20
22
|
export * from './tokens/index.js';
|
|
21
23
|
// Export utilities
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/** CDN base URL for all font assets */
|
|
8
8
|
export declare const FONT_CDN_BASE = "https://cdn.grove.place/fonts";
|
|
9
9
|
/** Font category for organizing fonts */
|
|
10
|
-
export type FontCategory = "default" | "accessibility" | "sans-serif" | "
|
|
10
|
+
export type FontCategory = "default" | "accessibility" | "sans-serif" | "monospace" | "display";
|
|
11
11
|
/** Font format for @font-face src declarations */
|
|
12
12
|
export type FontFormat = "truetype" | "opentype";
|
|
13
13
|
/** Complete font definition with metadata */
|