@autumnsgrove/groveengine 0.6.5 → 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.
@@ -64,7 +64,7 @@
64
64
  * Helper to get anchor key with headers context
65
65
  * @param {string} anchor
66
66
  */
67
- function getKey(anchor) {
67
+ function getKey(anchor: string) {
68
68
  return getAnchorKey(anchor, headers);
69
69
  }
70
70
 
@@ -72,16 +72,16 @@
72
72
  * Get items for a specific anchor
73
73
  * @param {string} anchor
74
74
  */
75
- function getItems(anchor) {
75
+ function getItems(anchor: string) {
76
76
  return getItemsForAnchor(gutterContent, anchor);
77
77
  }
78
78
 
79
79
  /**
80
80
  * Generate unique key for a gutter item
81
- * @param {GutterItem} item
81
+ * @param {GutterItemType} item
82
82
  * @param {number} index
83
83
  */
84
- function getItemKey(item, index) {
84
+ function getItemKey(item: GutterItemType, index: number): string {
85
85
  // Combine item properties to create a unique identifier
86
86
  const parts = [
87
87
  item.type || 'unknown',
@@ -103,16 +103,15 @@
103
103
 
104
104
  // Use getBoundingClientRect for accurate relative positioning
105
105
  // This works regardless of offset parent chains and CSS transforms
106
- const gutterRect = gutterElement.getBoundingClientRect();
106
+ const gutterRect = (gutterElement as HTMLElement).getBoundingClientRect();
107
107
 
108
108
  let lastBottom = 0; // Track the bottom edge of the last positioned item
109
- /** @type {string[]} */
110
- const newOverflowingAnchors = [];
109
+ const newOverflowingAnchors: string[] = [];
111
110
  const newPositions = { ...itemPositions };
112
111
 
113
112
  // Sort anchors by their position in the document
114
113
  const anchorPositions = uniqueAnchors.map(anchor => {
115
- const el = findAnchorElement(anchor, contentBodyElement ?? null, headers);
114
+ const el = findAnchorElement(anchor, (contentBodyElement ?? null) as HTMLElement | null, headers);
116
115
  if (!el && import.meta.env.DEV) {
117
116
  console.warn(`Anchor element not found for: ${anchor}`);
118
117
  }
@@ -168,8 +167,7 @@
168
167
 
169
168
  // Setup resize listener on mount with proper cleanup
170
169
  onMount(() => {
171
- /** @type {ReturnType<typeof setTimeout> | undefined} */
172
- let resizeTimeoutId;
170
+ let resizeTimeoutId: ReturnType<typeof setTimeout> | undefined;
173
171
  const handleResize = () => {
174
172
  clearTimeout(resizeTimeoutId);
175
173
  resizeTimeoutId = setTimeout(() => {
@@ -186,9 +184,8 @@
186
184
 
187
185
  // Setup copy button functionality for code blocks
188
186
  onMount(() => {
189
- /** @param {Event} event */
190
- const handleCopyClick = async (event) => {
191
- const button = /** @type {HTMLElement} */ (event.currentTarget);
187
+ const handleCopyClick = async (event: Event) => {
188
+ const button = event.currentTarget as HTMLElement;
192
189
  const codeText = button.getAttribute('data-code');
193
190
 
194
191
  if (!codeText) return;
@@ -251,20 +248,19 @@
251
248
  // Add IDs to headers and position mobile gutter items
252
249
  $effect(() => {
253
250
  // Track moved elements for cleanup
254
- /** @type {Array<{ element: HTMLElement, originalParent: HTMLElement | null, originalNextSibling: Node | null }>} */
255
- const movedElements = [];
251
+ const movedElements: Array<{ element: HTMLElement; originalParent: HTMLElement | null; originalNextSibling: Node | null }> = [];
256
252
 
257
253
  untrack(() => {
258
254
  if (!contentBodyElement) return;
259
255
 
260
256
  // First, add IDs to headers
261
257
  if (headers && headers.length > 0) {
262
- const headerElements = contentBodyElement.querySelectorAll('h1, h2, h3, h4, h5, h6');
263
- headerElements.forEach((el) => {
264
- const text = el.textContent?.trim() || '';
265
- const matchingHeader = headers.find(/** @param {Header} h */ (h) => h.text === text);
258
+ const headerElements = (contentBodyElement as HTMLElement).querySelectorAll('h1, h2, h3, h4, h5, h6');
259
+ headerElements.forEach((el: Element) => {
260
+ const text = (el as HTMLElement).textContent?.trim() || '';
261
+ const matchingHeader = headers.find((h: Header) => h.text === text);
266
262
  if (matchingHeader) {
267
- el.id = matchingHeader.id;
263
+ (el as HTMLElement).id = matchingHeader.id;
268
264
  }
269
265
  });
270
266
  }
@@ -279,7 +275,7 @@
279
275
  const originalParent = mobileGutterEl.parentElement;
280
276
  const originalNextSibling = mobileGutterEl.nextSibling;
281
277
 
282
- const targetEl = findAnchorElement(anchor, contentBodyElement, headers);
278
+ const targetEl = findAnchorElement(anchor, contentBodyElement as HTMLElement, headers);
283
279
 
284
280
  if (targetEl) {
285
281
  targetEl.insertAdjacentElement('afterend', mobileGutterEl);
@@ -308,12 +304,13 @@
308
304
  const updateHeight = () => {
309
305
  if (!contentBodyElement) return;
310
306
  // Get the bottom of content-body relative to the article
311
- const rect = contentBodyElement.getBoundingClientRect();
312
- const articleRect = contentBodyElement.closest('.content-article')?.getBoundingClientRect();
307
+ const rect = (contentBodyElement as HTMLElement).getBoundingClientRect();
308
+ const articleEl = (contentBodyElement as HTMLElement).closest('.content-article');
309
+ const articleRect = articleEl?.getBoundingClientRect();
313
310
  if (articleRect) {
314
311
  contentHeight = rect.bottom - articleRect.top;
315
312
  } else {
316
- contentHeight = contentBodyElement.offsetTop + contentBodyElement.offsetHeight;
313
+ contentHeight = (contentBodyElement as HTMLElement).offsetTop + (contentBodyElement as HTMLElement).offsetHeight;
317
314
  }
318
315
  };
319
316
  updateHeight();
@@ -348,7 +345,7 @@
348
345
  * @param {string} html
349
346
  * @param {string[]} overflowKeys
350
347
  */
351
- function injectReferenceMarkers(html, overflowKeys) {
348
+ function injectReferenceMarkers(html: string, overflowKeys: string[]): string {
352
349
  if (!overflowKeys || overflowKeys.length === 0 || typeof window === 'undefined') {
353
350
  return html;
354
351
  }
@@ -0,0 +1,158 @@
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
+ * Glass component for creating glassmorphism effects
8
+ *
9
+ * A reusable component that provides translucent, frosted-glass effects
10
+ * perfect for overlays, cards, navbars, and text containers while
11
+ * maintaining visibility of background elements.
12
+ *
13
+ * @example Basic usage
14
+ * ```svelte
15
+ * <Glass variant="card">
16
+ * <p>Content with glass background</p>
17
+ * </Glass>
18
+ * ```
19
+ *
20
+ * @example As a navbar
21
+ * ```svelte
22
+ * <Glass variant="surface" as="header" class="sticky top-0">
23
+ * <nav>...</nav>
24
+ * </Glass>
25
+ * ```
26
+ *
27
+ * @example Accent tint for callouts
28
+ * ```svelte
29
+ * <Glass variant="accent" intensity="light" class="p-6 rounded-xl">
30
+ * <p>Important message here</p>
31
+ * </Glass>
32
+ * ```
33
+ */
34
+
35
+ type Variant =
36
+ | "surface" // Headers, navbars - high opacity, subtle blur
37
+ | "overlay" // Modal backdrops - dark, medium blur
38
+ | "card" // Content cards - medium opacity, clean look
39
+ | "tint" // Text containers - light background for readability
40
+ | "accent" // Accent-colored glass for callouts/highlights
41
+ | "muted"; // Subtle background, barely visible
42
+
43
+ type Intensity =
44
+ | "none" // No blur (just transparency)
45
+ | "light" // backdrop-blur-sm (4px)
46
+ | "medium" // backdrop-blur (8px)
47
+ | "strong"; // backdrop-blur-md (12px)
48
+
49
+ type Element = "div" | "section" | "article" | "aside" | "header" | "footer" | "nav" | "main";
50
+
51
+ interface Props extends HTMLAttributes<HTMLElement> {
52
+ /** Visual style variant */
53
+ variant?: Variant;
54
+ /** Blur intensity */
55
+ intensity?: Intensity;
56
+ /** HTML element to render */
57
+ as?: Element;
58
+ /** Include subtle border */
59
+ border?: boolean;
60
+ /** Include shadow */
61
+ shadow?: boolean;
62
+ /** Additional CSS classes */
63
+ class?: string;
64
+ /** Content */
65
+ children?: Snippet;
66
+ }
67
+
68
+ let {
69
+ variant = "card",
70
+ intensity = "light",
71
+ as: element = "div",
72
+ border = true,
73
+ shadow = false,
74
+ class: className,
75
+ children,
76
+ ...restProps
77
+ }: Props = $props();
78
+
79
+ // Background colors per variant - warm grove tones, translucent for glass effect
80
+ const variantClasses: Record<Variant, string> = {
81
+ // High opacity for sticky headers/navbars (uses background color)
82
+ surface: "bg-background/90 dark:bg-background/90",
83
+
84
+ // Dark overlay for modals/sheets
85
+ overlay: "bg-black/50 dark:bg-black/60",
86
+
87
+ // Medium opacity for content cards - translucent with grove warmth
88
+ card: "bg-white/60 dark:bg-emerald-950/25",
89
+
90
+ // Light tint for text readability
91
+ tint: "bg-white/50 dark:bg-emerald-950/20",
92
+
93
+ // Accent-colored glass for highlights/callouts
94
+ accent: "bg-accent/25 dark:bg-accent/15",
95
+
96
+ // Barely visible, very subtle
97
+ muted: "bg-white/30 dark:bg-emerald-950/15"
98
+ };
99
+
100
+ // Blur intensity classes - default to medium blur for true glass effect
101
+ const intensityClasses: Record<Intensity, string> = {
102
+ none: "",
103
+ light: "backdrop-blur", // 8px
104
+ medium: "backdrop-blur-md", // 12px
105
+ strong: "backdrop-blur-lg" // 16px
106
+ };
107
+
108
+ // Border classes per variant - subtle borders that complement the glass
109
+ const borderClasses: Record<Variant, string> = {
110
+ surface: "border-border",
111
+ overlay: "border-white/10",
112
+ card: "border-white/40 dark:border-emerald-800/25",
113
+ tint: "border-white/30 dark:border-emerald-800/20",
114
+ accent: "border-accent/30 dark:border-accent/20",
115
+ muted: "border-white/20 dark:border-emerald-800/15"
116
+ };
117
+
118
+ // Shadow classes
119
+ const shadowClasses: Record<Variant, string> = {
120
+ surface: "shadow-sm",
121
+ overlay: "shadow-2xl",
122
+ card: "shadow-sm",
123
+ tint: "shadow-sm",
124
+ accent: "shadow-sm",
125
+ muted: ""
126
+ };
127
+
128
+ const computedClass = $derived(
129
+ cn(
130
+ variantClasses[variant],
131
+ intensityClasses[intensity],
132
+ border && `border ${borderClasses[variant]}`,
133
+ shadow && shadowClasses[variant],
134
+ className
135
+ )
136
+ );
137
+ </script>
138
+
139
+ <!--
140
+ Glassmorphism component for Grove
141
+
142
+ CSS Properties used:
143
+ - backdrop-filter: blur() - Creates the frosted glass effect
144
+ - Background with alpha - Semi-transparent backgrounds (e.g., bg-white/80)
145
+ - Border with alpha - Subtle borders that complement the glass effect
146
+
147
+ Browser Support:
148
+ - backdrop-filter is supported in all modern browsers
149
+ - Falls back gracefully to solid backgrounds in older browsers
150
+ -->
151
+
152
+ <svelte:element
153
+ this={element}
154
+ class={computedClass}
155
+ {...restProps}
156
+ >
157
+ {#if children}{@render children()}{/if}
158
+ </svelte:element>
@@ -0,0 +1,52 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ /**
4
+ * Glass component for creating glassmorphism effects
5
+ *
6
+ * A reusable component that provides translucent, frosted-glass effects
7
+ * perfect for overlays, cards, navbars, and text containers while
8
+ * maintaining visibility of background elements.
9
+ *
10
+ * @example Basic usage
11
+ * ```svelte
12
+ * <Glass variant="card">
13
+ * <p>Content with glass background</p>
14
+ * </Glass>
15
+ * ```
16
+ *
17
+ * @example As a navbar
18
+ * ```svelte
19
+ * <Glass variant="surface" as="header" class="sticky top-0">
20
+ * <nav>...</nav>
21
+ * </Glass>
22
+ * ```
23
+ *
24
+ * @example Accent tint for callouts
25
+ * ```svelte
26
+ * <Glass variant="accent" intensity="light" class="p-6 rounded-xl">
27
+ * <p>Important message here</p>
28
+ * </Glass>
29
+ * ```
30
+ */
31
+ type Variant = "surface" | "overlay" | "card" | "tint" | "accent" | "muted";
32
+ type Intensity = "none" | "light" | "medium" | "strong";
33
+ type Element = "div" | "section" | "article" | "aside" | "header" | "footer" | "nav" | "main";
34
+ interface Props extends HTMLAttributes<HTMLElement> {
35
+ /** Visual style variant */
36
+ variant?: Variant;
37
+ /** Blur intensity */
38
+ intensity?: Intensity;
39
+ /** HTML element to render */
40
+ as?: Element;
41
+ /** Include subtle border */
42
+ border?: boolean;
43
+ /** Include shadow */
44
+ shadow?: boolean;
45
+ /** Additional CSS classes */
46
+ class?: string;
47
+ /** Content */
48
+ children?: Snippet;
49
+ }
50
+ declare const Glass: import("svelte").Component<Props, {}, "">;
51
+ type Glass = ReturnType<typeof Glass>;
52
+ export default Glass;
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLButtonAttributes, HTMLAnchorAttributes } from "svelte/elements";
4
+ import { cn } from "../../utils";
5
+
6
+ /**
7
+ * GlassButton - A button with glassmorphism styling
8
+ *
9
+ * Beautiful translucent buttons with backdrop blur effects.
10
+ * Perfect for floating actions, overlays, and modern UI designs.
11
+ *
12
+ * @example Basic glass button
13
+ * ```svelte
14
+ * <GlassButton>Click me</GlassButton>
15
+ * ```
16
+ *
17
+ * @example Accent glass button
18
+ * ```svelte
19
+ * <GlassButton variant="accent">Subscribe</GlassButton>
20
+ * ```
21
+ *
22
+ * @example Ghost glass with icon
23
+ * ```svelte
24
+ * <GlassButton variant="ghost" size="icon">
25
+ * <X class="w-4 h-4" />
26
+ * </GlassButton>
27
+ * ```
28
+ */
29
+
30
+ type GlassVariant =
31
+ | "default" // Light translucent background
32
+ | "accent" // Accent-colored glass
33
+ | "dark" // Dark translucent background
34
+ | "ghost" // No background until hover
35
+ | "outline"; // Transparent with glass border
36
+
37
+ type ButtonSize = "sm" | "md" | "lg" | "icon";
38
+
39
+ interface Props extends Omit<HTMLButtonAttributes, "class"> {
40
+ variant?: GlassVariant;
41
+ size?: ButtonSize;
42
+ disabled?: boolean;
43
+ href?: string;
44
+ class?: string;
45
+ children?: Snippet;
46
+ ref?: HTMLButtonElement | HTMLAnchorElement | null;
47
+ }
48
+
49
+ let {
50
+ variant = "default",
51
+ size = "md",
52
+ disabled = false,
53
+ href,
54
+ class: className,
55
+ children,
56
+ ref = $bindable(null),
57
+ type = "button",
58
+ ...restProps
59
+ }: Props = $props();
60
+
61
+ // Base styles shared by all variants - medium blur for glass effect
62
+ const baseClasses = `
63
+ inline-flex items-center justify-center gap-2
64
+ font-medium rounded-lg
65
+ transition-all duration-200
66
+ backdrop-blur-md
67
+ outline-none
68
+ focus-visible:ring-2 focus-visible:ring-accent/50 focus-visible:ring-offset-2
69
+ disabled:opacity-50 disabled:pointer-events-none
70
+ [&_svg]:w-4 [&_svg]:h-4 [&_svg]:shrink-0
71
+ `.trim().replace(/\s+/g, ' ');
72
+
73
+ // Variant-specific styles - warm grove tones with true glass transparency
74
+ const variantClasses: Record<GlassVariant, string> = {
75
+ default: `
76
+ bg-white/60 dark:bg-emerald-950/25
77
+ border border-white/40 dark:border-emerald-800/25
78
+ text-foreground
79
+ hover:bg-white/75 dark:hover:bg-emerald-950/35
80
+ hover:border-white/50 dark:hover:border-emerald-700/30
81
+ shadow-sm hover:shadow-md
82
+ `.trim().replace(/\s+/g, ' '),
83
+
84
+ accent: `
85
+ bg-accent/70 dark:bg-accent/60
86
+ border border-accent/40 dark:border-accent/30
87
+ text-white
88
+ hover:bg-accent/85 dark:hover:bg-accent/75
89
+ hover:border-accent/60 dark:hover:border-accent/50
90
+ shadow-sm hover:shadow-md shadow-accent/20
91
+ `.trim().replace(/\s+/g, ' '),
92
+
93
+ dark: `
94
+ bg-slate-900/50 dark:bg-slate-950/50
95
+ border border-slate-700/30 dark:border-slate-600/30
96
+ text-white
97
+ hover:bg-slate-900/65 dark:hover:bg-slate-950/65
98
+ hover:border-slate-600/40 dark:hover:border-slate-500/35
99
+ shadow-md hover:shadow-lg
100
+ `.trim().replace(/\s+/g, ' '),
101
+
102
+ ghost: `
103
+ bg-transparent
104
+ border border-transparent
105
+ text-foreground
106
+ hover:bg-white/40 dark:hover:bg-emerald-950/25
107
+ hover:border-white/25 dark:hover:border-emerald-800/20
108
+ `.trim().replace(/\s+/g, ' '),
109
+
110
+ outline: `
111
+ bg-transparent
112
+ border border-white/40 dark:border-emerald-800/30
113
+ text-foreground
114
+ hover:bg-white/25 dark:hover:bg-emerald-950/20
115
+ hover:border-white/55 dark:hover:border-emerald-700/40
116
+ `.trim().replace(/\s+/g, ' ')
117
+ };
118
+
119
+ // Size-specific styles
120
+ const sizeClasses: Record<ButtonSize, string> = {
121
+ sm: "h-8 px-3 text-sm",
122
+ md: "h-10 px-4 text-sm",
123
+ lg: "h-12 px-6 text-base",
124
+ icon: "h-10 w-10 p-0"
125
+ };
126
+
127
+ const computedClass = $derived(
128
+ cn(
129
+ baseClasses,
130
+ variantClasses[variant],
131
+ sizeClasses[size],
132
+ className
133
+ )
134
+ );
135
+ </script>
136
+
137
+ {#if href && !disabled}
138
+ <a
139
+ bind:this={ref}
140
+ {href}
141
+ class={computedClass}
142
+ aria-disabled={disabled}
143
+ {...restProps}
144
+ >
145
+ {#if children}{@render children()}{/if}
146
+ </a>
147
+ {:else}
148
+ <button
149
+ bind:this={ref}
150
+ {type}
151
+ {disabled}
152
+ class={computedClass}
153
+ {...restProps}
154
+ >
155
+ {#if children}{@render children()}{/if}
156
+ </button>
157
+ {/if}
@@ -0,0 +1,39 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLButtonAttributes } from "svelte/elements";
3
+ /**
4
+ * GlassButton - A button with glassmorphism styling
5
+ *
6
+ * Beautiful translucent buttons with backdrop blur effects.
7
+ * Perfect for floating actions, overlays, and modern UI designs.
8
+ *
9
+ * @example Basic glass button
10
+ * ```svelte
11
+ * <GlassButton>Click me</GlassButton>
12
+ * ```
13
+ *
14
+ * @example Accent glass button
15
+ * ```svelte
16
+ * <GlassButton variant="accent">Subscribe</GlassButton>
17
+ * ```
18
+ *
19
+ * @example Ghost glass with icon
20
+ * ```svelte
21
+ * <GlassButton variant="ghost" size="icon">
22
+ * <X class="w-4 h-4" />
23
+ * </GlassButton>
24
+ * ```
25
+ */
26
+ type GlassVariant = "default" | "accent" | "dark" | "ghost" | "outline";
27
+ type ButtonSize = "sm" | "md" | "lg" | "icon";
28
+ interface Props extends Omit<HTMLButtonAttributes, "class"> {
29
+ variant?: GlassVariant;
30
+ size?: ButtonSize;
31
+ disabled?: boolean;
32
+ href?: string;
33
+ class?: string;
34
+ children?: Snippet;
35
+ ref?: HTMLButtonElement | HTMLAnchorElement | null;
36
+ }
37
+ declare const GlassButton: import("svelte").Component<Props, {}, "ref">;
38
+ type GlassButton = ReturnType<typeof GlassButton>;
39
+ export default GlassButton;
@@ -0,0 +1,160 @@
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
+ * GlassCard - A card component with glassmorphism styling
8
+ *
9
+ * Beautiful translucent cards with backdrop blur effects.
10
+ * Includes optional header, footer, and hoverable state.
11
+ *
12
+ * @example Basic glass card
13
+ * ```svelte
14
+ * <GlassCard title="Settings" description="Manage your preferences">
15
+ * <p>Card content here</p>
16
+ * </GlassCard>
17
+ * ```
18
+ *
19
+ * @example Accent card with footer
20
+ * ```svelte
21
+ * <GlassCard variant="accent" hoverable>
22
+ * {#snippet header()}<CustomHeader />{/snippet}
23
+ * Content here
24
+ * {#snippet footer()}<Button>Save</Button>{/snippet}
25
+ * </GlassCard>
26
+ * ```
27
+ */
28
+
29
+ type GlassVariant =
30
+ | "default" // Light translucent background
31
+ | "accent" // Accent-colored glass
32
+ | "dark" // Dark translucent background
33
+ | "muted" // Very subtle, barely visible
34
+ | "frosted"; // Stronger blur effect, more opaque
35
+
36
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
37
+ variant?: GlassVariant;
38
+ title?: string;
39
+ description?: string;
40
+ hoverable?: boolean;
41
+ border?: boolean;
42
+ class?: string;
43
+ header?: Snippet;
44
+ footer?: Snippet;
45
+ children?: Snippet;
46
+ }
47
+
48
+ let {
49
+ variant = "default",
50
+ title,
51
+ description,
52
+ hoverable = false,
53
+ border = true,
54
+ class: className,
55
+ header,
56
+ footer,
57
+ children,
58
+ ...restProps
59
+ }: Props = $props();
60
+
61
+ // Variant-specific styles - warm grove tones with true glass transparency
62
+ const variantClasses: Record<GlassVariant, string> = {
63
+ default: `
64
+ bg-white/60 dark:bg-emerald-950/25
65
+ backdrop-blur-md
66
+ `.trim().replace(/\s+/g, ' '),
67
+
68
+ accent: `
69
+ bg-accent/20 dark:bg-accent/15
70
+ backdrop-blur-md
71
+ `.trim().replace(/\s+/g, ' '),
72
+
73
+ dark: `
74
+ bg-slate-900/40 dark:bg-slate-950/40
75
+ backdrop-blur-md
76
+ text-white
77
+ `.trim().replace(/\s+/g, ' '),
78
+
79
+ muted: `
80
+ bg-white/30 dark:bg-emerald-950/15
81
+ backdrop-blur
82
+ `.trim().replace(/\s+/g, ' '),
83
+
84
+ frosted: `
85
+ bg-white/70 dark:bg-emerald-950/35
86
+ backdrop-blur-lg
87
+ `.trim().replace(/\s+/g, ' ')
88
+ };
89
+
90
+ // Border colors per variant - subtle, warm borders
91
+ const borderClasses: Record<GlassVariant, string> = {
92
+ default: "border-white/40 dark:border-emerald-800/25",
93
+ accent: "border-accent/30 dark:border-accent/20",
94
+ dark: "border-slate-700/30 dark:border-slate-600/30",
95
+ muted: "border-white/20 dark:border-emerald-800/15",
96
+ frosted: "border-white/50 dark:border-emerald-800/30"
97
+ };
98
+
99
+ // Hover styles - slightly more visible on hover
100
+ const hoverClasses: Record<GlassVariant, string> = {
101
+ default: "hover:bg-white/70 dark:hover:bg-emerald-950/35 hover:shadow-lg hover:border-white/50 dark:hover:border-emerald-700/30",
102
+ accent: "hover:bg-accent/30 dark:hover:bg-accent/25 hover:shadow-lg hover:shadow-accent/10 hover:border-accent/40",
103
+ dark: "hover:bg-slate-900/50 dark:hover:bg-slate-950/50 hover:shadow-xl hover:border-slate-600/40",
104
+ muted: "hover:bg-white/40 dark:hover:bg-emerald-950/25 hover:shadow-md hover:border-white/30",
105
+ frosted: "hover:bg-white/80 dark:hover:bg-emerald-950/45 hover:shadow-lg hover:border-white/60"
106
+ };
107
+
108
+ const computedClass = $derived(
109
+ cn(
110
+ "rounded-xl transition-all duration-200",
111
+ variantClasses[variant],
112
+ border && `border ${borderClasses[variant]}`,
113
+ hoverable && `cursor-pointer ${hoverClasses[variant]}`,
114
+ "shadow-sm",
115
+ className
116
+ )
117
+ );
118
+
119
+ // Text color adjustments for dark variant
120
+ const titleClass = $derived(
121
+ variant === "dark"
122
+ ? "text-white"
123
+ : "text-foreground"
124
+ );
125
+
126
+ const descriptionClass = $derived(
127
+ variant === "dark"
128
+ ? "text-slate-300"
129
+ : "text-muted-foreground"
130
+ );
131
+ </script>
132
+
133
+ <div class={computedClass} {...restProps}>
134
+ {#if header || title || description}
135
+ <div class="px-6 py-4 {(children || footer) ? 'border-b border-inherit' : ''}">
136
+ {#if header}
137
+ {@render header()}
138
+ {:else}
139
+ {#if title}
140
+ <h3 class="text-lg font-semibold {titleClass}">{title}</h3>
141
+ {/if}
142
+ {#if description}
143
+ <p class="text-sm {descriptionClass} mt-1">{description}</p>
144
+ {/if}
145
+ {/if}
146
+ </div>
147
+ {/if}
148
+
149
+ {#if children}
150
+ <div class="px-6 py-4">
151
+ {@render children()}
152
+ </div>
153
+ {/if}
154
+
155
+ {#if footer}
156
+ <div class="px-6 py-4 border-t border-inherit">
157
+ {@render footer()}
158
+ </div>
159
+ {/if}
160
+ </div>