@autumnsgrove/groveengine 0.8.0 → 0.8.5
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 +1093 -0
- package/dist/components/WispPanel.svelte.d.ts +49 -0
- 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/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/BodoniModa.svelte +17 -0
- package/dist/ui/components/typography/BodoniModa.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/Cormorant.svelte +17 -0
- package/dist/ui/components/typography/Cormorant.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/EBGaramond.svelte +17 -0
- package/dist/ui/components/typography/EBGaramond.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/Fraunces.svelte +17 -0
- package/dist/ui/components/typography/Fraunces.svelte.d.ts +10 -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/InstrumentSans.svelte +17 -0
- package/dist/ui/components/typography/InstrumentSans.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/Lora.svelte +17 -0
- package/dist/ui/components/typography/Lora.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Luciole.svelte +17 -0
- package/dist/ui/components/typography/Luciole.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Manrope.svelte +17 -0
- package/dist/ui/components/typography/Manrope.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Merriweather.svelte +17 -0
- package/dist/ui/components/typography/Merriweather.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Nunito.svelte +17 -0
- package/dist/ui/components/typography/Nunito.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 +23 -0
- package/dist/ui/components/typography/index.js +42 -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/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 +17 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { default as FontProvider } from './FontProvider.svelte';
|
|
2
|
+
export { default as Lexend } from './Lexend.svelte';
|
|
3
|
+
export { default as Atkinson } from './Atkinson.svelte';
|
|
4
|
+
export { default as OpenDyslexic } from './OpenDyslexic.svelte';
|
|
5
|
+
export { default as Luciole } from './Luciole.svelte';
|
|
6
|
+
export { default as Nunito } from './Nunito.svelte';
|
|
7
|
+
export { default as Quicksand } from './Quicksand.svelte';
|
|
8
|
+
export { default as Manrope } from './Manrope.svelte';
|
|
9
|
+
export { default as InstrumentSans } from './InstrumentSans.svelte';
|
|
10
|
+
export { default as PlusJakartaSans } from './PlusJakartaSans.svelte';
|
|
11
|
+
export { default as Cormorant } from './Cormorant.svelte';
|
|
12
|
+
export { default as BodoniModa } from './BodoniModa.svelte';
|
|
13
|
+
export { default as Lora } from './Lora.svelte';
|
|
14
|
+
export { default as EBGaramond } from './EBGaramond.svelte';
|
|
15
|
+
export { default as Merriweather } from './Merriweather.svelte';
|
|
16
|
+
export { default as Fraunces } from './Fraunces.svelte';
|
|
17
|
+
export { default as IBMPlexMono } from './IBMPlexMono.svelte';
|
|
18
|
+
export { default as Cozette } from './Cozette.svelte';
|
|
19
|
+
export { default as Alagard } from './Alagard.svelte';
|
|
20
|
+
export { default as Calistoga } from './Calistoga.svelte';
|
|
21
|
+
export { default as Caveat } from './Caveat.svelte';
|
|
22
|
+
export { fonts, fontById, fontMap, getFontUrl, getFontStack, getFontsByCategory, generateFontFace, generateAllFontFaces, FONT_CDN_BASE, DEFAULT_FONT, FONT_COUNT, fontCategoryLabels, type FontId, type FontCategory, type FontFormat, type FontDefinition, } from '../../tokens/fonts.js';
|
|
23
|
+
export declare const TYPOGRAPHY_VERSION = "0.1.0";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// GroveUI - Typography Components
|
|
2
|
+
//
|
|
3
|
+
// Font wrapper components for scoped font application.
|
|
4
|
+
// Each component automatically loads the font from CDN and applies it to children.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// import { Alagard, IBMPlexMono, Caveat } from '@autumnsgrove/groveengine/ui/typography';
|
|
8
|
+
//
|
|
9
|
+
// <Alagard as="h1">Fantasy Header</Alagard>
|
|
10
|
+
// <IBMPlexMono as="code">console.log('code')</IBMPlexMono>
|
|
11
|
+
// <Caveat as="p" class="text-xl">Handwritten note</Caveat>
|
|
12
|
+
// Base provider (for dynamic font selection)
|
|
13
|
+
export { default as FontProvider } from './FontProvider.svelte';
|
|
14
|
+
// Default font
|
|
15
|
+
export { default as Lexend } from './Lexend.svelte';
|
|
16
|
+
// Accessibility fonts
|
|
17
|
+
export { default as Atkinson } from './Atkinson.svelte';
|
|
18
|
+
export { default as OpenDyslexic } from './OpenDyslexic.svelte';
|
|
19
|
+
export { default as Luciole } from './Luciole.svelte';
|
|
20
|
+
// Modern sans-serif fonts
|
|
21
|
+
export { default as Nunito } from './Nunito.svelte';
|
|
22
|
+
export { default as Quicksand } from './Quicksand.svelte';
|
|
23
|
+
export { default as Manrope } from './Manrope.svelte';
|
|
24
|
+
export { default as InstrumentSans } from './InstrumentSans.svelte';
|
|
25
|
+
export { default as PlusJakartaSans } from './PlusJakartaSans.svelte';
|
|
26
|
+
// Serif fonts
|
|
27
|
+
export { default as Cormorant } from './Cormorant.svelte';
|
|
28
|
+
export { default as BodoniModa } from './BodoniModa.svelte';
|
|
29
|
+
export { default as Lora } from './Lora.svelte';
|
|
30
|
+
export { default as EBGaramond } from './EBGaramond.svelte';
|
|
31
|
+
export { default as Merriweather } from './Merriweather.svelte';
|
|
32
|
+
export { default as Fraunces } from './Fraunces.svelte';
|
|
33
|
+
// Monospace fonts
|
|
34
|
+
export { default as IBMPlexMono } from './IBMPlexMono.svelte';
|
|
35
|
+
export { default as Cozette } from './Cozette.svelte';
|
|
36
|
+
// Display/special fonts
|
|
37
|
+
export { default as Alagard } from './Alagard.svelte';
|
|
38
|
+
export { default as Calistoga } from './Calistoga.svelte';
|
|
39
|
+
export { default as Caveat } from './Caveat.svelte';
|
|
40
|
+
// Re-export font tokens for convenience
|
|
41
|
+
export { fonts, fontById, fontMap, getFontUrl, getFontStack, getFontsByCategory, generateFontFace, generateAllFontFaces, FONT_CDN_BASE, DEFAULT_FONT, FONT_COUNT, fontCategoryLabels, } from '../../tokens/fonts.js';
|
|
42
|
+
export const TYPOGRAPHY_VERSION = '0.1.0';
|
|
@@ -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
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vineyard Components
|
|
3
|
+
* Re-exports from @autumnsgrove/vineyard package
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { VineyardLayout, FeatureCard, StatusBadge } from '@autumnsgrove/groveengine/vineyard';
|
|
7
|
+
*/
|
|
8
|
+
export { VineyardLayout, FeatureCard, StatusBadge, DemoContainer, CodeExample, TierGate, RoadmapSection, } from "@autumnsgrove/vineyard/vineyard";
|
|
9
|
+
export type { VineyardStatus, GroveTool, GroveTier, VineyardLayoutProps, FeatureCardProps, StatusBadgeProps, DemoContainerProps, CodeExampleProps, TierGateProps, RoadmapSectionProps, } from "@autumnsgrove/vineyard/vineyard";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vineyard Components
|
|
3
|
+
* Re-exports from @autumnsgrove/vineyard package
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { VineyardLayout, FeatureCard, StatusBadge } from '@autumnsgrove/groveengine/vineyard';
|
|
7
|
+
*/
|
|
8
|
+
export { VineyardLayout, FeatureCard, StatusBadge, DemoContainer, CodeExample, TierGate, RoadmapSection, } from "@autumnsgrove/vineyard/vineyard";
|
package/dist/utils/csrf.js
CHANGED
|
@@ -44,7 +44,8 @@ export function validateCSRF(request) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const origin = request.headers.get("origin");
|
|
47
|
-
|
|
47
|
+
// Check X-Forwarded-Host first (set by grove-router proxy), then fall back to host
|
|
48
|
+
const host = request.headers.get("x-forwarded-host") || request.headers.get("host");
|
|
48
49
|
|
|
49
50
|
// Allow same-origin requests
|
|
50
51
|
if (origin) {
|
|
@@ -65,9 +66,11 @@ export function validateCSRF(request) {
|
|
|
65
66
|
return false;
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// Check if origin matches host OR is a *.grove.place subdomain
|
|
68
70
|
const hostMatches = host && originUrl.host === host;
|
|
71
|
+
const isGroveDomain = originUrl.hostname.endsWith(".grove.place") || originUrl.hostname === "grove.place";
|
|
69
72
|
|
|
70
|
-
if (!isLocalhost && !hostMatches) {
|
|
73
|
+
if (!isLocalhost && !hostMatches && !isGroveDomain) {
|
|
71
74
|
return false;
|
|
72
75
|
}
|
|
73
76
|
} catch {
|