@autumnsgrove/groveengine 0.6.4 → 0.6.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.
Files changed (38) hide show
  1. package/dist/auth/session.d.ts +2 -2
  2. package/dist/components/admin/FloatingToolbar.svelte +373 -0
  3. package/dist/components/admin/FloatingToolbar.svelte.d.ts +17 -0
  4. package/dist/components/admin/MarkdownEditor.svelte +26 -347
  5. package/dist/components/admin/MarkdownEditor.svelte.d.ts +1 -1
  6. package/dist/components/admin/composables/index.d.ts +0 -2
  7. package/dist/components/admin/composables/index.js +0 -2
  8. package/dist/components/custom/MobileTOC.svelte +20 -13
  9. package/dist/components/quota/UpgradePrompt.svelte +1 -1
  10. package/dist/server/services/database.d.ts +138 -0
  11. package/dist/server/services/database.js +234 -0
  12. package/dist/server/services/index.d.ts +5 -1
  13. package/dist/server/services/index.js +24 -2
  14. package/dist/server/services/turnstile.d.ts +66 -0
  15. package/dist/server/services/turnstile.js +131 -0
  16. package/dist/server/services/users.d.ts +104 -0
  17. package/dist/server/services/users.js +158 -0
  18. package/dist/styles/README.md +50 -0
  19. package/dist/styles/vine-pattern.css +24 -0
  20. package/dist/types/turnstile.d.ts +42 -0
  21. package/dist/ui/components/forms/TurnstileWidget.svelte +111 -0
  22. package/dist/ui/components/forms/TurnstileWidget.svelte.d.ts +14 -0
  23. package/dist/ui/components/primitives/dialog/dialog-overlay.svelte +1 -1
  24. package/dist/ui/components/primitives/sheet/sheet-overlay.svelte +1 -1
  25. package/dist/ui/components/ui/Logo.svelte +161 -23
  26. package/dist/ui/components/ui/Logo.svelte.d.ts +4 -10
  27. package/dist/ui/tokens/fonts.d.ts +69 -0
  28. package/dist/ui/tokens/fonts.js +341 -0
  29. package/dist/ui/tokens/index.d.ts +6 -5
  30. package/dist/ui/tokens/index.js +7 -6
  31. package/package.json +22 -21
  32. package/static/fonts/alagard.ttf +0 -0
  33. package/static/robots.txt +487 -0
  34. package/LICENSE +0 -378
  35. package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +0 -87
  36. package/dist/components/admin/composables/useCommandPalette.svelte.js +0 -158
  37. package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +0 -104
  38. package/dist/components/admin/composables/useSlashCommands.svelte.js +0 -215
@@ -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>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Grove Design System - Font Tokens
3
+ *
4
+ * Complete font catalog with metadata, CDN URLs, and helper utilities.
5
+ * All fonts are served from cdn.grove.place for optimal performance.
6
+ */
7
+ /** CDN base URL for all font assets */
8
+ export declare const FONT_CDN_BASE = "https://cdn.grove.place/fonts";
9
+ /** Font category for organizing fonts */
10
+ export type FontCategory = "default" | "accessibility" | "sans-serif" | "serif" | "monospace" | "display";
11
+ /** Font format for @font-face src declarations */
12
+ export type FontFormat = "truetype" | "opentype";
13
+ /** Complete font definition with metadata */
14
+ export interface FontDefinition {
15
+ /** Unique identifier used in database and fontMap */
16
+ id: string;
17
+ /** Display name shown to users */
18
+ name: string;
19
+ /** Font file name on CDN */
20
+ file: string;
21
+ /** Font format (truetype or opentype) */
22
+ format: FontFormat;
23
+ /** CSS font-family name (may include spaces) */
24
+ fontFamily: string;
25
+ /** Category for organizing fonts */
26
+ category: FontCategory;
27
+ /** Brief description of the font's purpose/style */
28
+ description: string;
29
+ /** CSS fallback stack */
30
+ fallback: string[];
31
+ }
32
+ /**
33
+ * Complete catalog of available fonts
34
+ * These fonts are served from cdn.grove.place
35
+ */
36
+ export declare const fonts: readonly FontDefinition[];
37
+ /** All valid font IDs */
38
+ export type FontId = (typeof fonts)[number]["id"];
39
+ /** Map of font IDs to their definitions */
40
+ export declare const fontById: Record<FontId, FontDefinition>;
41
+ /** Get fonts by category */
42
+ export declare function getFontsByCategory(category: FontCategory): FontDefinition[];
43
+ /** Get CDN URL for a font */
44
+ export declare function getFontUrl(fontIdOrFile: string): string;
45
+ /** Get complete CSS font-family value with fallbacks */
46
+ export declare function getFontStack(fontId: FontId): string;
47
+ /**
48
+ * Generate @font-face CSS for a single font
49
+ * @param fontId - Font ID to generate CSS for
50
+ * @returns CSS @font-face declaration string
51
+ */
52
+ export declare function generateFontFace(fontId: FontId): string;
53
+ /**
54
+ * Generate @font-face CSS for multiple fonts
55
+ * @param fontIds - Array of font IDs (defaults to all fonts)
56
+ * @returns CSS string with all @font-face declarations
57
+ */
58
+ export declare function generateAllFontFaces(fontIds?: FontId[]): string;
59
+ /**
60
+ * Font map matching the format used in +layout.svelte
61
+ * Maps font IDs to their complete CSS font-family values
62
+ */
63
+ export declare const fontMap: Record<FontId, string>;
64
+ /** Default font ID */
65
+ export declare const DEFAULT_FONT: FontId;
66
+ /** Total number of available fonts */
67
+ export declare const FONT_COUNT: number;
68
+ /** Font categories with human-readable labels */
69
+ export declare const fontCategoryLabels: Record<FontCategory, string>;
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Grove Design System - Font Tokens
3
+ *
4
+ * Complete font catalog with metadata, CDN URLs, and helper utilities.
5
+ * All fonts are served from cdn.grove.place for optimal performance.
6
+ */
7
+ /** CDN base URL for all font assets */
8
+ export const FONT_CDN_BASE = "https://cdn.grove.place/fonts";
9
+ /**
10
+ * Complete catalog of available fonts
11
+ * These fonts are served from cdn.grove.place
12
+ */
13
+ export const fonts = [
14
+ // Default
15
+ {
16
+ id: "lexend",
17
+ name: "Lexend",
18
+ file: "Lexend-Regular.ttf",
19
+ format: "truetype",
20
+ fontFamily: "Lexend",
21
+ category: "default",
22
+ description: "Modern, highly readable sans-serif. Grove default.",
23
+ fallback: [
24
+ "-apple-system",
25
+ "BlinkMacSystemFont",
26
+ "Segoe UI",
27
+ "Roboto",
28
+ "sans-serif",
29
+ ],
30
+ },
31
+ // Accessibility fonts
32
+ {
33
+ id: "atkinson",
34
+ name: "Atkinson Hyperlegible",
35
+ file: "AtkinsonHyperlegible-Regular.ttf",
36
+ format: "truetype",
37
+ fontFamily: "Atkinson Hyperlegible",
38
+ category: "accessibility",
39
+ description: "Designed for low vision readers. Maximum character distinction.",
40
+ fallback: [
41
+ "-apple-system",
42
+ "BlinkMacSystemFont",
43
+ "Segoe UI",
44
+ "Roboto",
45
+ "sans-serif",
46
+ ],
47
+ },
48
+ {
49
+ id: "opendyslexic",
50
+ name: "OpenDyslexic",
51
+ file: "OpenDyslexic-Regular.otf",
52
+ format: "opentype",
53
+ fontFamily: "OpenDyslexic",
54
+ category: "accessibility",
55
+ description: "Weighted bottoms reduce letter confusion for dyslexic readers.",
56
+ fallback: [
57
+ "-apple-system",
58
+ "BlinkMacSystemFont",
59
+ "Segoe UI",
60
+ "Roboto",
61
+ "sans-serif",
62
+ ],
63
+ },
64
+ {
65
+ id: "luciole",
66
+ name: "Luciole",
67
+ file: "Luciole-Regular.ttf",
68
+ format: "truetype",
69
+ fontFamily: "Luciole",
70
+ category: "accessibility",
71
+ description: "French accessibility font designed for visually impaired readers.",
72
+ fallback: [
73
+ "-apple-system",
74
+ "BlinkMacSystemFont",
75
+ "Segoe UI",
76
+ "Roboto",
77
+ "sans-serif",
78
+ ],
79
+ },
80
+ // Modern sans-serif fonts
81
+ {
82
+ id: "nunito",
83
+ name: "Nunito",
84
+ file: "Nunito-Regular.ttf",
85
+ format: "truetype",
86
+ fontFamily: "Nunito",
87
+ category: "sans-serif",
88
+ description: "Friendly rounded sans-serif. Warm and approachable.",
89
+ fallback: [
90
+ "-apple-system",
91
+ "BlinkMacSystemFont",
92
+ "Segoe UI",
93
+ "Roboto",
94
+ "sans-serif",
95
+ ],
96
+ },
97
+ {
98
+ id: "quicksand",
99
+ name: "Quicksand",
100
+ file: "Quicksand-Regular.ttf",
101
+ format: "truetype",
102
+ fontFamily: "Quicksand",
103
+ category: "sans-serif",
104
+ description: "Geometric sans with rounded terminals. Light and modern.",
105
+ fallback: [
106
+ "-apple-system",
107
+ "BlinkMacSystemFont",
108
+ "Segoe UI",
109
+ "Roboto",
110
+ "sans-serif",
111
+ ],
112
+ },
113
+ {
114
+ id: "manrope",
115
+ name: "Manrope",
116
+ file: "Manrope-Regular.ttf",
117
+ format: "truetype",
118
+ fontFamily: "Manrope",
119
+ category: "sans-serif",
120
+ description: "Professional geometric sans. Clean and contemporary.",
121
+ fallback: [
122
+ "-apple-system",
123
+ "BlinkMacSystemFont",
124
+ "Segoe UI",
125
+ "Roboto",
126
+ "sans-serif",
127
+ ],
128
+ },
129
+ {
130
+ id: "instrument-sans",
131
+ name: "Instrument Sans",
132
+ file: "InstrumentSans-Regular.ttf",
133
+ format: "truetype",
134
+ fontFamily: "Instrument Sans",
135
+ category: "sans-serif",
136
+ description: "Low contrast sans with humanist touches. Elegant simplicity.",
137
+ fallback: [
138
+ "-apple-system",
139
+ "BlinkMacSystemFont",
140
+ "Segoe UI",
141
+ "Roboto",
142
+ "sans-serif",
143
+ ],
144
+ },
145
+ {
146
+ id: "plus-jakarta-sans",
147
+ name: "Plus Jakarta Sans",
148
+ file: "PlusJakartaSans-Regular.ttf",
149
+ format: "truetype",
150
+ fontFamily: "Plus Jakarta Sans",
151
+ category: "sans-serif",
152
+ description: "Contemporary geometric sans. Balanced and versatile.",
153
+ fallback: [
154
+ "-apple-system",
155
+ "BlinkMacSystemFont",
156
+ "Segoe UI",
157
+ "Roboto",
158
+ "sans-serif",
159
+ ],
160
+ },
161
+ // Serif fonts
162
+ {
163
+ id: "cormorant",
164
+ name: "Cormorant",
165
+ file: "Cormorant-Regular.ttf",
166
+ format: "truetype",
167
+ fontFamily: "Cormorant",
168
+ category: "serif",
169
+ description: "Elegant display serif inspired by Garamond. Refined and classic.",
170
+ fallback: ["Georgia", "Times New Roman", "serif"],
171
+ },
172
+ {
173
+ id: "bodoni-moda",
174
+ name: "Bodoni Moda",
175
+ file: "BodoniModa-Regular.ttf",
176
+ format: "truetype",
177
+ fontFamily: "Bodoni Moda",
178
+ category: "serif",
179
+ description: "High contrast modern serif. Bold and sophisticated.",
180
+ fallback: ["Georgia", "Times New Roman", "serif"],
181
+ },
182
+ {
183
+ id: "lora",
184
+ name: "Lora",
185
+ file: "Lora-Regular.ttf",
186
+ format: "truetype",
187
+ fontFamily: "Lora",
188
+ category: "serif",
189
+ description: "Well-balanced contemporary serif. Excellent for body text.",
190
+ fallback: ["Georgia", "Times New Roman", "serif"],
191
+ },
192
+ {
193
+ id: "eb-garamond",
194
+ name: "EB Garamond",
195
+ file: "EBGaramond-Regular.ttf",
196
+ format: "truetype",
197
+ fontFamily: "EB Garamond",
198
+ category: "serif",
199
+ description: "Revival of classic Garamond. Timeless book typography.",
200
+ fallback: ["Georgia", "Times New Roman", "serif"],
201
+ },
202
+ {
203
+ id: "merriweather",
204
+ name: "Merriweather",
205
+ file: "Merriweather-Regular.ttf",
206
+ format: "truetype",
207
+ fontFamily: "Merriweather",
208
+ category: "serif",
209
+ description: "Designed for screen reading. Excellent legibility.",
210
+ fallback: ["Georgia", "Times New Roman", "serif"],
211
+ },
212
+ {
213
+ id: "fraunces",
214
+ name: "Fraunces",
215
+ file: "Fraunces-Regular.ttf",
216
+ format: "truetype",
217
+ fontFamily: "Fraunces",
218
+ category: "serif",
219
+ description: 'Soft serif with "wonky" optical axes. Warm personality.',
220
+ fallback: ["Georgia", "Times New Roman", "serif"],
221
+ },
222
+ // Monospace fonts
223
+ {
224
+ id: "ibm-plex-mono",
225
+ name: "IBM Plex Mono",
226
+ file: "IBMPlexMono-Regular.ttf",
227
+ format: "truetype",
228
+ fontFamily: "IBM Plex Mono",
229
+ category: "monospace",
230
+ description: "Corporate monospace with human warmth. Great for code.",
231
+ fallback: ["Courier New", "Consolas", "monospace"],
232
+ },
233
+ {
234
+ id: "cozette",
235
+ name: "Cozette",
236
+ file: "CozetteVector.ttf",
237
+ format: "truetype",
238
+ fontFamily: "Cozette",
239
+ category: "monospace",
240
+ description: "Bitmap-style vector font. Retro terminal aesthetic.",
241
+ fallback: ["Courier New", "Consolas", "monospace"],
242
+ },
243
+ // Display/special fonts
244
+ {
245
+ id: "alagard",
246
+ name: "Alagard",
247
+ file: "alagard.ttf",
248
+ format: "truetype",
249
+ fontFamily: "Alagard",
250
+ category: "display",
251
+ description: "Pixel art medieval display font. Fantasy and gaming.",
252
+ fallback: ["fantasy", "cursive"],
253
+ },
254
+ {
255
+ id: "calistoga",
256
+ name: "Calistoga",
257
+ file: "Calistoga-Regular.ttf",
258
+ format: "truetype",
259
+ fontFamily: "Calistoga",
260
+ category: "display",
261
+ description: "Casual brush serif. Friendly headlines.",
262
+ fallback: ["Georgia", "serif"],
263
+ },
264
+ {
265
+ id: "caveat",
266
+ name: "Caveat",
267
+ file: "Caveat-Regular.ttf",
268
+ format: "truetype",
269
+ fontFamily: "Caveat",
270
+ category: "display",
271
+ description: "Handwritten script. Personal and informal.",
272
+ fallback: ["cursive", "sans-serif"],
273
+ },
274
+ ];
275
+ /** Map of font IDs to their definitions */
276
+ export const fontById = Object.fromEntries(fonts.map((font) => [font.id, font]));
277
+ /** Get fonts by category */
278
+ export function getFontsByCategory(category) {
279
+ return fonts.filter((font) => font.category === category);
280
+ }
281
+ /** Get CDN URL for a font */
282
+ export function getFontUrl(fontIdOrFile) {
283
+ // If it's a font ID, look up the file
284
+ const font = fonts.find((f) => f.id === fontIdOrFile);
285
+ const file = font ? font.file : fontIdOrFile;
286
+ return `${FONT_CDN_BASE}/${file}`;
287
+ }
288
+ /** Get complete CSS font-family value with fallbacks */
289
+ export function getFontStack(fontId) {
290
+ const font = fontById[fontId];
291
+ if (!font)
292
+ return fontById.lexend.fallback.join(", ");
293
+ const primary = font.fontFamily.includes(" ")
294
+ ? `'${font.fontFamily}'`
295
+ : font.fontFamily;
296
+ return [primary, ...font.fallback].join(", ");
297
+ }
298
+ /**
299
+ * Generate @font-face CSS for a single font
300
+ * @param fontId - Font ID to generate CSS for
301
+ * @returns CSS @font-face declaration string
302
+ */
303
+ export function generateFontFace(fontId) {
304
+ const font = fontById[fontId];
305
+ if (!font)
306
+ return "";
307
+ return `@font-face {
308
+ font-family: '${font.fontFamily}';
309
+ src: url('${getFontUrl(font.file)}') format('${font.format}');
310
+ font-weight: normal;
311
+ font-style: normal;
312
+ font-display: swap;
313
+ }`;
314
+ }
315
+ /**
316
+ * Generate @font-face CSS for multiple fonts
317
+ * @param fontIds - Array of font IDs (defaults to all fonts)
318
+ * @returns CSS string with all @font-face declarations
319
+ */
320
+ export function generateAllFontFaces(fontIds) {
321
+ const ids = fontIds ?? fonts.map((f) => f.id);
322
+ return ids.map(generateFontFace).filter(Boolean).join("\n\n");
323
+ }
324
+ /**
325
+ * Font map matching the format used in +layout.svelte
326
+ * Maps font IDs to their complete CSS font-family values
327
+ */
328
+ export const fontMap = Object.fromEntries(fonts.map((font) => [font.id, getFontStack(font.id)]));
329
+ /** Default font ID */
330
+ export const DEFAULT_FONT = "lexend";
331
+ /** Total number of available fonts */
332
+ export const FONT_COUNT = fonts.length;
333
+ /** Font categories with human-readable labels */
334
+ export const fontCategoryLabels = {
335
+ default: "Default",
336
+ accessibility: "Accessibility",
337
+ "sans-serif": "Sans-Serif",
338
+ serif: "Serif",
339
+ monospace: "Monospace",
340
+ display: "Display & Special",
341
+ };
@@ -1,6 +1,7 @@
1
- export * from './colors.js';
2
- export * from './typography.js';
3
- export * from './spacing.js';
4
- export * from './effects.js';
5
- export * from './animation.js';
1
+ export * from "./colors.js";
2
+ export * from "./typography.js";
3
+ export * from "./spacing.js";
4
+ export * from "./effects.js";
5
+ export * from "./animation.js";
6
+ export * from "./fonts.js";
6
7
  export declare const TOKENS_VERSION = "0.2.0";