@autumnsgrove/groveengine 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/auth/index.d.ts +1 -2
  2. package/dist/auth/index.js +8 -4
  3. package/dist/auth/session.d.ts +14 -33
  4. package/dist/auth/session.js +5 -103
  5. package/dist/components/admin/FloatingToolbar.svelte +373 -0
  6. package/dist/components/admin/FloatingToolbar.svelte.d.ts +17 -0
  7. package/dist/components/admin/MarkdownEditor.svelte +26 -347
  8. package/dist/components/admin/MarkdownEditor.svelte.d.ts +1 -1
  9. package/dist/components/admin/composables/index.d.ts +0 -2
  10. package/dist/components/admin/composables/index.js +0 -2
  11. package/dist/components/custom/ContentWithGutter.svelte +22 -25
  12. package/dist/components/custom/MobileTOC.svelte +20 -13
  13. package/dist/components/quota/UpgradePrompt.svelte +1 -1
  14. package/dist/server/services/database.d.ts +138 -0
  15. package/dist/server/services/database.js +234 -0
  16. package/dist/server/services/index.d.ts +5 -1
  17. package/dist/server/services/index.js +24 -2
  18. package/dist/server/services/turnstile.d.ts +66 -0
  19. package/dist/server/services/turnstile.js +131 -0
  20. package/dist/server/services/users.d.ts +104 -0
  21. package/dist/server/services/users.js +158 -0
  22. package/dist/styles/README.md +50 -0
  23. package/dist/styles/vine-pattern.css +24 -0
  24. package/dist/types/turnstile.d.ts +42 -0
  25. package/dist/ui/components/forms/TurnstileWidget.svelte +111 -0
  26. package/dist/ui/components/forms/TurnstileWidget.svelte.d.ts +14 -0
  27. package/dist/ui/components/primitives/dialog/dialog-overlay.svelte +1 -1
  28. package/dist/ui/components/primitives/sheet/sheet-overlay.svelte +1 -1
  29. package/dist/ui/components/ui/Glass.svelte +158 -0
  30. package/dist/ui/components/ui/Glass.svelte.d.ts +52 -0
  31. package/dist/ui/components/ui/GlassButton.svelte +157 -0
  32. package/dist/ui/components/ui/GlassButton.svelte.d.ts +39 -0
  33. package/dist/ui/components/ui/GlassCard.svelte +160 -0
  34. package/dist/ui/components/ui/GlassCard.svelte.d.ts +39 -0
  35. package/dist/ui/components/ui/GlassConfirmDialog.svelte +208 -0
  36. package/dist/ui/components/ui/GlassConfirmDialog.svelte.d.ts +52 -0
  37. package/dist/ui/components/ui/GlassOverlay.svelte +93 -0
  38. package/dist/ui/components/ui/GlassOverlay.svelte.d.ts +33 -0
  39. package/dist/ui/components/ui/Logo.svelte +161 -23
  40. package/dist/ui/components/ui/Logo.svelte.d.ts +4 -10
  41. package/dist/ui/components/ui/index.d.ts +5 -0
  42. package/dist/ui/components/ui/index.js +6 -0
  43. package/dist/ui/styles/grove.css +136 -0
  44. package/dist/ui/tokens/fonts.d.ts +69 -0
  45. package/dist/ui/tokens/fonts.js +341 -0
  46. package/dist/ui/tokens/index.d.ts +6 -5
  47. package/dist/ui/tokens/index.js +7 -6
  48. package/dist/utils/gutter.d.ts +2 -8
  49. package/dist/utils/markdown.d.ts +1 -0
  50. package/dist/utils/markdown.js +32 -11
  51. package/package.json +1 -1
  52. package/static/robots.txt +520 -0
  53. package/dist/auth/jwt.d.ts +0 -20
  54. package/dist/auth/jwt.js +0 -123
  55. package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +0 -87
  56. package/dist/components/admin/composables/useCommandPalette.svelte.js +0 -158
  57. package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +0 -104
  58. package/dist/components/admin/composables/useSlashCommands.svelte.js +0 -215
@@ -0,0 +1,208 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { cn } from "../../utils";
4
+ import { fade, scale } from "svelte/transition";
5
+ import { AlertTriangle, Trash2, HelpCircle } from "lucide-svelte";
6
+ import Button from "./Button.svelte";
7
+
8
+ /**
9
+ * GlassConfirmDialog - A confirmation dialog with glassmorphism styling
10
+ *
11
+ * Perfect for destructive actions like delete, or any action that needs user confirmation.
12
+ * Features a glass-effect card over a blurred overlay.
13
+ *
14
+ * @example Basic confirmation
15
+ * ```svelte
16
+ * <GlassConfirmDialog
17
+ * bind:open={showDeleteDialog}
18
+ * title="Delete Post"
19
+ * message="Are you sure you want to delete this post? This cannot be undone."
20
+ * confirmLabel="Delete"
21
+ * variant="danger"
22
+ * onconfirm={handleDelete}
23
+ * />
24
+ * ```
25
+ *
26
+ * @example Custom content
27
+ * ```svelte
28
+ * <GlassConfirmDialog bind:open={showDialog} title="Confirm Changes" onconfirm={save}>
29
+ * <p>You have unsaved changes. Would you like to save them?</p>
30
+ * </GlassConfirmDialog>
31
+ * ```
32
+ */
33
+
34
+ type DialogVariant = "default" | "danger" | "warning";
35
+
36
+ interface Props {
37
+ /** Whether the dialog is open (bindable) */
38
+ open?: boolean;
39
+ /** Dialog title */
40
+ title: string;
41
+ /** Dialog message (if not using children) */
42
+ message?: string;
43
+ /** Confirm button label */
44
+ confirmLabel?: string;
45
+ /** Cancel button label */
46
+ cancelLabel?: string;
47
+ /** Dialog variant (affects styling and icon) */
48
+ variant?: DialogVariant;
49
+ /** Whether the confirm action is loading */
50
+ loading?: boolean;
51
+ /** Called when user confirms */
52
+ onconfirm?: () => void | Promise<void>;
53
+ /** Called when user cancels or closes */
54
+ oncancel?: () => void;
55
+ /** Custom content (overrides message) */
56
+ children?: Snippet;
57
+ }
58
+
59
+ let {
60
+ open = $bindable(false),
61
+ title,
62
+ message,
63
+ confirmLabel = "Confirm",
64
+ cancelLabel = "Cancel",
65
+ variant = "default",
66
+ loading = false,
67
+ onconfirm,
68
+ oncancel,
69
+ children
70
+ }: Props = $props();
71
+
72
+ // Variant-specific styling
73
+ const variantConfig = {
74
+ default: {
75
+ icon: HelpCircle,
76
+ iconClass: "text-accent-muted",
77
+ confirmVariant: "primary" as const
78
+ },
79
+ danger: {
80
+ icon: Trash2,
81
+ iconClass: "text-red-500 dark:text-red-400",
82
+ confirmVariant: "danger" as const
83
+ },
84
+ warning: {
85
+ icon: AlertTriangle,
86
+ iconClass: "text-amber-500 dark:text-amber-400",
87
+ confirmVariant: "primary" as const
88
+ }
89
+ };
90
+
91
+ const config = $derived(variantConfig[variant]);
92
+
93
+ function handleCancel() {
94
+ open = false;
95
+ oncancel?.();
96
+ }
97
+
98
+ async function handleConfirm() {
99
+ try {
100
+ await onconfirm?.();
101
+ open = false;
102
+ } catch (error) {
103
+ // Don't close on error - let the caller handle it
104
+ console.error('Confirm action failed:', error);
105
+ }
106
+ }
107
+
108
+ function handleKeydown(event: KeyboardEvent) {
109
+ if (event.key === "Escape") {
110
+ handleCancel();
111
+ }
112
+ }
113
+
114
+ function handleBackdropClick(event: MouseEvent) {
115
+ // Only close if clicking the backdrop itself, not the dialog
116
+ if (event.target === event.currentTarget) {
117
+ handleCancel();
118
+ }
119
+ }
120
+ </script>
121
+
122
+ <svelte:window onkeydown={handleKeydown} />
123
+
124
+ {#if open}
125
+ <!-- Backdrop with glass effect -->
126
+ <div
127
+ class="fixed inset-0 z-50 flex items-center justify-center p-4"
128
+ onclick={handleBackdropClick}
129
+ role="dialog"
130
+ aria-modal="true"
131
+ aria-labelledby="confirm-dialog-title"
132
+ transition:fade={{ duration: 150 }}
133
+ >
134
+ <!-- Dark overlay with blur -->
135
+ <div
136
+ class="absolute inset-0 bg-black/50 dark:bg-black/60 backdrop-blur-sm"
137
+ aria-hidden="true"
138
+ ></div>
139
+
140
+ <!-- Dialog card -->
141
+ <div
142
+ class={cn(
143
+ "relative z-10 w-full max-w-md",
144
+ "bg-white/80 dark:bg-slate-900/80",
145
+ "backdrop-blur-xl",
146
+ "border border-white/40 dark:border-slate-700/40",
147
+ "rounded-2xl shadow-2xl",
148
+ "overflow-hidden"
149
+ )}
150
+ transition:scale={{ duration: 150, start: 0.95 }}
151
+ >
152
+ <!-- Header with icon -->
153
+ <div class="px-6 pt-6 pb-4 flex items-start gap-4">
154
+ <div class={cn(
155
+ "flex-shrink-0 p-3 rounded-full",
156
+ variant === "danger" && "bg-red-100 dark:bg-red-900/30",
157
+ variant === "warning" && "bg-amber-100 dark:bg-amber-900/30",
158
+ variant === "default" && "bg-accent/10 dark:bg-accent/20"
159
+ )}>
160
+ <svelte:component this={config.icon} class={cn("w-6 h-6", config.iconClass)} />
161
+ </div>
162
+ <div class="flex-1 min-w-0">
163
+ <h3
164
+ id="confirm-dialog-title"
165
+ class="text-lg font-semibold text-foreground leading-tight"
166
+ >
167
+ {title}
168
+ </h3>
169
+ {#if message && !children}
170
+ <p class="mt-2 text-sm text-muted-foreground leading-relaxed">
171
+ {message}
172
+ </p>
173
+ {/if}
174
+ {#if children}
175
+ <div class="mt-2 text-sm text-muted-foreground">
176
+ {@render children()}
177
+ </div>
178
+ {/if}
179
+ </div>
180
+ </div>
181
+
182
+ <!-- Footer with actions -->
183
+ <div class="px-6 py-4 bg-slate-50/50 dark:bg-slate-800/30 border-t border-white/20 dark:border-slate-700/30 flex justify-end gap-3">
184
+ <Button
185
+ variant="ghost"
186
+ onclick={handleCancel}
187
+ disabled={loading}
188
+ >
189
+ {cancelLabel}
190
+ </Button>
191
+ <Button
192
+ variant={config.confirmVariant}
193
+ onclick={handleConfirm}
194
+ disabled={loading}
195
+ >
196
+ {#if loading}
197
+ <span class="inline-flex items-center gap-2">
198
+ <span class="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin"></span>
199
+ Processing...
200
+ </span>
201
+ {:else}
202
+ {confirmLabel}
203
+ {/if}
204
+ </Button>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ {/if}
@@ -0,0 +1,52 @@
1
+ import type { Snippet } from "svelte";
2
+ /**
3
+ * GlassConfirmDialog - A confirmation dialog with glassmorphism styling
4
+ *
5
+ * Perfect for destructive actions like delete, or any action that needs user confirmation.
6
+ * Features a glass-effect card over a blurred overlay.
7
+ *
8
+ * @example Basic confirmation
9
+ * ```svelte
10
+ * <GlassConfirmDialog
11
+ * bind:open={showDeleteDialog}
12
+ * title="Delete Post"
13
+ * message="Are you sure you want to delete this post? This cannot be undone."
14
+ * confirmLabel="Delete"
15
+ * variant="danger"
16
+ * onconfirm={handleDelete}
17
+ * />
18
+ * ```
19
+ *
20
+ * @example Custom content
21
+ * ```svelte
22
+ * <GlassConfirmDialog bind:open={showDialog} title="Confirm Changes" onconfirm={save}>
23
+ * <p>You have unsaved changes. Would you like to save them?</p>
24
+ * </GlassConfirmDialog>
25
+ * ```
26
+ */
27
+ type DialogVariant = "default" | "danger" | "warning";
28
+ interface Props {
29
+ /** Whether the dialog is open (bindable) */
30
+ open?: boolean;
31
+ /** Dialog title */
32
+ title: string;
33
+ /** Dialog message (if not using children) */
34
+ message?: string;
35
+ /** Confirm button label */
36
+ confirmLabel?: string;
37
+ /** Cancel button label */
38
+ cancelLabel?: string;
39
+ /** Dialog variant (affects styling and icon) */
40
+ variant?: DialogVariant;
41
+ /** Whether the confirm action is loading */
42
+ loading?: boolean;
43
+ /** Called when user confirms */
44
+ onconfirm?: () => void | Promise<void>;
45
+ /** Called when user cancels or closes */
46
+ oncancel?: () => void;
47
+ /** Custom content (overrides message) */
48
+ children?: Snippet;
49
+ }
50
+ declare const GlassConfirmDialog: import("svelte").Component<Props, {}, "open">;
51
+ type GlassConfirmDialog = ReturnType<typeof GlassConfirmDialog>;
52
+ export default GlassConfirmDialog;
@@ -0,0 +1,93 @@
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
+ * GlassOverlay - A fullscreen overlay with glassmorphism effect
8
+ *
9
+ * Perfect for modal backdrops, loading screens, and focus overlays.
10
+ * Provides a dark translucent backdrop with blur effect.
11
+ *
12
+ * @example Basic overlay
13
+ * ```svelte
14
+ * {#if showModal}
15
+ * <GlassOverlay onclick={closeModal} />
16
+ * {/if}
17
+ * ```
18
+ *
19
+ * @example Custom intensity
20
+ * ```svelte
21
+ * <GlassOverlay variant="light" intensity="strong" />
22
+ * ```
23
+ */
24
+
25
+ type OverlayVariant =
26
+ | "dark" // Dark overlay (default for modals)
27
+ | "light" // Light overlay
28
+ | "accent"; // Tinted with accent color
29
+
30
+ type BlurIntensity =
31
+ | "none" // No blur
32
+ | "light" // 4px blur
33
+ | "medium" // 8px blur
34
+ | "strong"; // 12px blur
35
+
36
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
37
+ variant?: OverlayVariant;
38
+ intensity?: BlurIntensity;
39
+ /** Whether clicking the overlay should be interactive (for close handlers) */
40
+ interactive?: boolean;
41
+ class?: string;
42
+ children?: Snippet;
43
+ }
44
+
45
+ let {
46
+ variant = "dark",
47
+ intensity = "light",
48
+ interactive = true,
49
+ class: className,
50
+ children,
51
+ ...restProps
52
+ }: Props = $props();
53
+
54
+ // Variant backgrounds
55
+ const variantClasses: Record<OverlayVariant, string> = {
56
+ dark: "bg-black/50 dark:bg-black/60",
57
+ light: "bg-white/50 dark:bg-slate-900/50",
58
+ accent: "bg-accent/30 dark:bg-accent/20"
59
+ };
60
+
61
+ // Blur intensities
62
+ const intensityClasses: Record<BlurIntensity, string> = {
63
+ none: "",
64
+ light: "backdrop-blur-sm",
65
+ medium: "backdrop-blur",
66
+ strong: "backdrop-blur-md"
67
+ };
68
+
69
+ const computedClass = $derived(
70
+ cn(
71
+ "fixed inset-0 z-50",
72
+ variantClasses[variant],
73
+ intensityClasses[intensity],
74
+ interactive && "cursor-pointer",
75
+ // Animation classes for when used with transitions
76
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
77
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
78
+ className
79
+ )
80
+ );
81
+ </script>
82
+
83
+ <div
84
+ class={computedClass}
85
+ role={interactive ? "button" : "presentation"}
86
+ tabindex={interactive ? 0 : -1}
87
+ aria-label={interactive ? "Close overlay" : undefined}
88
+ {...restProps}
89
+ >
90
+ {#if children}
91
+ {@render children()}
92
+ {/if}
93
+ </div>
@@ -0,0 +1,33 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ /**
4
+ * GlassOverlay - A fullscreen overlay with glassmorphism effect
5
+ *
6
+ * Perfect for modal backdrops, loading screens, and focus overlays.
7
+ * Provides a dark translucent backdrop with blur effect.
8
+ *
9
+ * @example Basic overlay
10
+ * ```svelte
11
+ * {#if showModal}
12
+ * <GlassOverlay onclick={closeModal} />
13
+ * {/if}
14
+ * ```
15
+ *
16
+ * @example Custom intensity
17
+ * ```svelte
18
+ * <GlassOverlay variant="light" intensity="strong" />
19
+ * ```
20
+ */
21
+ type OverlayVariant = "dark" | "light" | "accent";
22
+ type BlurIntensity = "none" | "light" | "medium" | "strong";
23
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
24
+ variant?: OverlayVariant;
25
+ intensity?: BlurIntensity;
26
+ /** Whether clicking the overlay should be interactive (for close handlers) */
27
+ interactive?: boolean;
28
+ class?: string;
29
+ children?: Snippet;
30
+ }
31
+ declare const GlassOverlay: import("svelte").Component<Props, {}, "">;
32
+ type GlassOverlay = ReturnType<typeof GlassOverlay>;
33
+ export default GlassOverlay;
@@ -7,8 +7,21 @@
7
7
  * when placed in an accent-colored context, or can be overridden.
8
8
  *
9
9
  * The trunk defaults to Grove's classic bark brown (#5d4037).
10
+ *
11
+ * @example Loading state (breathing animation)
12
+ * ```svelte
13
+ * <Logo breathing />
14
+ * <Logo breathing breathingSpeed="slow" />
15
+ * ```
16
+ * Note: breathing is intended for single loading indicators, not lists.
10
17
  */
11
18
 
19
+ import { tweened } from 'svelte/motion';
20
+ import { cubicInOut } from 'svelte/easing';
21
+ import { browser } from '$app/environment';
22
+
23
+ type BreathingSpeed = 'slow' | 'normal' | 'fast';
24
+
12
25
  interface Props {
13
26
  class?: string;
14
27
  /** Foliage color - defaults to currentColor (inherits accent) */
@@ -19,8 +32,10 @@
19
32
  monochrome?: boolean;
20
33
  /** Add subtle sway animation */
21
34
  animate?: boolean;
22
- /** Add breathing animation (for loading states) */
35
+ /** Add breathing animation (for loading states, not lists) */
23
36
  breathing?: boolean;
37
+ /** Breathing animation speed - 'slow' (1500ms), 'normal' (800ms), 'fast' (400ms) */
38
+ breathingSpeed?: BreathingSpeed;
24
39
  }
25
40
 
26
41
  let {
@@ -29,9 +44,28 @@
29
44
  trunkColor,
30
45
  monochrome = false,
31
46
  animate = false,
32
- breathing = false
47
+ breathing = false,
48
+ breathingSpeed = 'normal'
33
49
  }: Props = $props();
34
50
 
51
+ // Breathing speed presets (duration per half-cycle in ms)
52
+ const BREATHING_SPEEDS = {
53
+ slow: 1500, // 3s full cycle - calm, meditative
54
+ normal: 800, // 1.6s full cycle - balanced
55
+ fast: 400 // 0.8s full cycle - urgent
56
+ } as const;
57
+
58
+ // Respect user's reduced motion preference (reactive to system changes)
59
+ const reducedMotionQuery = browser ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
60
+ let prefersReducedMotion = $state(reducedMotionQuery?.matches ?? false);
61
+
62
+ $effect(() => {
63
+ if (!reducedMotionQuery) return;
64
+ const handler = (e: MediaQueryListEvent) => { prefersReducedMotion = e.matches; };
65
+ reducedMotionQuery.addEventListener('change', handler);
66
+ return () => reducedMotionQuery.removeEventListener('change', handler);
67
+ });
68
+
35
69
  // Classic bark brown from the nature palette
36
70
  const BARK_BROWN = '#5d4037';
37
71
 
@@ -41,8 +75,85 @@
41
75
  ? foliageColor
42
76
  : (trunkColor ?? BARK_BROWN);
43
77
 
44
- // Build animation classes (using $derived for reactivity)
45
- const animationClass = $derived(breathing ? 'grove-logo-breathe' : (animate ? 'grove-logo-sway' : ''));
78
+ // Breathing animation using tweened store (duration set dynamically in $effect)
79
+ const breathValue = tweened(0, { easing: cubicInOut });
80
+
81
+ // Animation loop for breathing effect
82
+ // Re-runs when breathing or breathingSpeed changes, ensuring reactive duration updates
83
+ $effect(() => {
84
+ const duration = BREATHING_SPEEDS[breathingSpeed];
85
+
86
+ // Disable animation if breathing is off or user prefers reduced motion
87
+ if (!breathing || prefersReducedMotion) {
88
+ // Use half the breathing duration for smoother exit transition
89
+ breathValue.set(0, { duration: Math.min(duration / 2, 300) });
90
+ return;
91
+ }
92
+
93
+ let cancelled = false;
94
+
95
+ async function pulse() {
96
+ while (!cancelled) {
97
+ await breathValue.set(1, { duration });
98
+ if (cancelled) break;
99
+ await breathValue.set(0, { duration });
100
+ if (cancelled) break;
101
+ }
102
+ }
103
+
104
+ pulse();
105
+
106
+ return () => {
107
+ cancelled = true;
108
+ };
109
+ });
110
+
111
+ // Expansion values for breathing animation (in SVG units, tied to viewBox 417×512.238)
112
+ // These are absolute values within the SVG coordinate system, so they scale
113
+ // proportionally with the logo regardless of rendered size.
114
+ const expansion = $derived($breathValue * 22);
115
+ const diagExpansion = $derived($breathValue * 16); // ~16px at 45° angles
116
+
117
+ // Individual branch transforms
118
+ const leftTransform = $derived(`translate(${-expansion}, 0)`);
119
+ const rightTransform = $derived(`translate(${expansion}, 0)`);
120
+ const topTransform = $derived(`translate(0, ${-expansion})`);
121
+ const topLeftTransform = $derived(`translate(${-diagExpansion}, ${-diagExpansion})`);
122
+ const topRightTransform = $derived(`translate(${diagExpansion}, ${-diagExpansion})`);
123
+ const bottomLeftTransform = $derived(`translate(${-diagExpansion}, ${diagExpansion})`);
124
+ const bottomRightTransform = $derived(`translate(${diagExpansion}, ${diagExpansion})`);
125
+
126
+ // Build animation classes (sway only, breathing uses transforms)
127
+ const animationClass = $derived(animate && !breathing ? 'grove-logo-sway' : '');
128
+
129
+ // Decomposed foliage paths (8 pieces) for breathing animation
130
+ // Original path: "M0 173.468h126.068l-89.622-85.44 49.591-50.985 85.439 87.829V0h74.086v124.872L331 37.243l49.552 50.785-89.58 85.24H417v70.502H290.252l90.183 87.629L331 381.192 208.519 258.11 86.037 381.192l-49.591-49.591 90.218-87.631H0v-70.502z"
131
+ // Decomposed by tracing path commands and isolating geometric boundaries where arms meet center.
132
+ // If modifying, ensure pieces align at rest (breathValue=0) to match the original silhouette.
133
+
134
+ // Center anchor - the hub where all branches connect (stays stationary)
135
+ const centerPath = "M126 173.468 L171.476 124.872 L171.476 173.468 L126 173.468 M245.562 124.872 L290.972 173.268 L245.562 173.268 L245.562 124.872 M126.664 243.97 L171.476 243.97 L171.476 173.468 L126 173.468 L126.664 243.97 M290.252 243.77 L245.562 243.77 L245.562 173.268 L290.972 173.268 L290.252 243.77 M171.476 243.97 L208.519 258.11 L245.562 243.77 L245.562 173.268 L171.476 173.468 L171.476 243.97";
136
+
137
+ // Left horizontal bar
138
+ const leftBarPath = "M0 173.468 L126 173.468 L126.664 243.97 L0 243.97 Z";
139
+
140
+ // Right horizontal bar
141
+ const rightBarPath = "M290.972 173.268 L417 173.268 L417 243.77 L290.252 243.77 Z";
142
+
143
+ // Top vertical bar
144
+ const topBarPath = "M171.476 0 L245.562 0 L245.562 124.872 L171.476 124.872 Z";
145
+
146
+ // Top-left diagonal branch (arrow shape)
147
+ const topLeftDiagPath = "M126.068 173.468 L36.446 88.028 L86.037 37.043 L171.476 124.872 Z";
148
+
149
+ // Top-right diagonal branch (arrow shape)
150
+ const topRightDiagPath = "M245.562 124.872 L331 37.243 L380.552 88.028 L290.972 173.268 Z";
151
+
152
+ // Bottom-left diagonal branch (arrow shape)
153
+ const bottomLeftDiagPath = "M126.664 243.97 L36.446 331.601 L86.037 381.192 L208.519 258.11 L171.476 243.97 Z";
154
+
155
+ // Bottom-right diagonal branch (arrow shape)
156
+ const bottomRightDiagPath = "M290.252 243.77 L380.435 331.399 L331 381.192 L208.519 258.11 L245.562 243.77 Z";
46
157
  </script>
47
158
 
48
159
  <svg
@@ -51,10 +162,53 @@
51
162
  viewBox="0 0 417 512.238"
52
163
  aria-label="Grove logo"
53
164
  >
54
- <!-- Trunk -->
165
+ <!-- Trunk (always static) -->
55
166
  <path fill={actualTrunkColor} d="M171.274 344.942h74.09v167.296h-74.09V344.942z"/>
56
- <!-- Foliage -->
57
- <path fill={foliageColor} d="M0 173.468h126.068l-89.622-85.44 49.591-50.985 85.439 87.829V0h74.086v124.872L331 37.243l49.552 50.785-89.58 85.24H417v70.502H290.252l90.183 87.629L331 381.192 208.519 258.11 86.037 381.192l-49.591-49.591 90.218-87.631H0v-70.502z"/>
167
+
168
+ {#if breathing}
169
+ <!-- Decomposed foliage with breathing animation -->
170
+
171
+ <!-- Center anchor (stationary) -->
172
+ <path fill={foliageColor} d={centerPath}/>
173
+
174
+ <!-- Left horizontal bar -->
175
+ <g transform={leftTransform}>
176
+ <path fill={foliageColor} d={leftBarPath}/>
177
+ </g>
178
+
179
+ <!-- Right horizontal bar -->
180
+ <g transform={rightTransform}>
181
+ <path fill={foliageColor} d={rightBarPath}/>
182
+ </g>
183
+
184
+ <!-- Top vertical bar -->
185
+ <g transform={topTransform}>
186
+ <path fill={foliageColor} d={topBarPath}/>
187
+ </g>
188
+
189
+ <!-- Top-left diagonal -->
190
+ <g transform={topLeftTransform}>
191
+ <path fill={foliageColor} d={topLeftDiagPath}/>
192
+ </g>
193
+
194
+ <!-- Top-right diagonal -->
195
+ <g transform={topRightTransform}>
196
+ <path fill={foliageColor} d={topRightDiagPath}/>
197
+ </g>
198
+
199
+ <!-- Bottom-left diagonal -->
200
+ <g transform={bottomLeftTransform}>
201
+ <path fill={foliageColor} d={bottomLeftDiagPath}/>
202
+ </g>
203
+
204
+ <!-- Bottom-right diagonal -->
205
+ <g transform={bottomRightTransform}>
206
+ <path fill={foliageColor} d={bottomRightDiagPath}/>
207
+ </g>
208
+ {:else}
209
+ <!-- Original single foliage path (for non-breathing state) -->
210
+ <path fill={foliageColor} d="M0 173.468h126.068l-89.622-85.44 49.591-50.985 85.439 87.829V0h74.086v124.872L331 37.243l49.552 50.785-89.58 85.24H417v70.502H290.252l90.183 87.629L331 381.192 208.519 258.11 86.037 381.192l-49.591-49.591 90.218-87.631H0v-70.502z"/>
211
+ {/if}
58
212
  </svg>
59
213
 
60
214
  <style>
@@ -63,24 +217,8 @@
63
217
  50% { transform: rotate(1deg); }
64
218
  }
65
219
 
66
- @keyframes grove-logo-breathe {
67
- 0%, 100% {
68
- transform: scale(1);
69
- opacity: 0.7;
70
- }
71
- 50% {
72
- transform: scale(1.05);
73
- opacity: 1;
74
- }
75
- }
76
-
77
220
  .grove-logo-sway {
78
221
  transform-origin: center bottom;
79
222
  animation: grove-logo-sway 4s ease-in-out infinite;
80
223
  }
81
-
82
- .grove-logo-breathe {
83
- transform-origin: center center;
84
- animation: grove-logo-breathe 2s ease-in-out infinite;
85
- }
86
224
  </style>
@@ -1,12 +1,4 @@
1
- /**
2
- * Grove Logo Component
3
- *
4
- * A logo that respects the user's accent color by default.
5
- * The foliage uses `currentColor` which inherits from --accent-color
6
- * when placed in an accent-colored context, or can be overridden.
7
- *
8
- * The trunk defaults to Grove's classic bark brown (#5d4037).
9
- */
1
+ type BreathingSpeed = 'slow' | 'normal' | 'fast';
10
2
  interface Props {
11
3
  class?: string;
12
4
  /** Foliage color - defaults to currentColor (inherits accent) */
@@ -17,8 +9,10 @@ interface Props {
17
9
  monochrome?: boolean;
18
10
  /** Add subtle sway animation */
19
11
  animate?: boolean;
20
- /** Add breathing animation (for loading states) */
12
+ /** Add breathing animation (for loading states, not lists) */
21
13
  breathing?: boolean;
14
+ /** Breathing animation speed - 'slow' (1500ms), 'normal' (800ms), 'fast' (400ms) */
15
+ breathingSpeed?: BreathingSpeed;
22
16
  }
23
17
  declare const Logo: import("svelte").Component<Props, {}, "">;
24
18
  type Logo = ReturnType<typeof Logo>;
@@ -15,6 +15,11 @@ export { default as Table } from './Table.svelte';
15
15
  export { default as CollapsibleSection } from './CollapsibleSection.svelte';
16
16
  export { default as Logo } from './Logo.svelte';
17
17
  export { default as LogoLoader } from './LogoLoader.svelte';
18
+ export { default as Glass } from './Glass.svelte';
19
+ export { default as GlassButton } from './GlassButton.svelte';
20
+ export { default as GlassCard } from './GlassCard.svelte';
21
+ export { default as GlassConfirmDialog } from './GlassConfirmDialog.svelte';
22
+ export { default as GlassOverlay } from './GlassOverlay.svelte';
18
23
  export { TableHeader, TableBody, TableRow, TableCell, TableHead, TableFooter, TableCaption, } from '../primitives/table';
19
24
  export * from './toast.js';
20
25
  export declare const UI_VERSION = "0.2.0";
@@ -23,6 +23,12 @@ export { default as Table } from './Table.svelte';
23
23
  export { default as CollapsibleSection } from './CollapsibleSection.svelte';
24
24
  export { default as Logo } from './Logo.svelte';
25
25
  export { default as LogoLoader } from './LogoLoader.svelte';
26
+ // Glass suite - glassmorphism components
27
+ export { default as Glass } from './Glass.svelte';
28
+ export { default as GlassButton } from './GlassButton.svelte';
29
+ export { default as GlassCard } from './GlassCard.svelte';
30
+ export { default as GlassConfirmDialog } from './GlassConfirmDialog.svelte';
31
+ export { default as GlassOverlay } from './GlassOverlay.svelte';
26
32
  // Table sub-components (from primitives)
27
33
  export { TableHeader, TableBody, TableRow, TableCell, TableHead, TableFooter, TableCaption, } from '../primitives/table';
28
34
  // Toast utility