typography-toolkit 1.0.0 → 1.2.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.
package/README.md CHANGED
@@ -7,6 +7,9 @@ Letter-by-letter text animations with proximity-based disintegration effects. Cr
7
7
  - **Letter-by-Letter Control** - Each letter is a separate DOM element with independent animations
8
8
  - **Base Animations** - Falling, splitting, glitching, and floating animations
9
9
  - **Proximity-Based Disintegration** - Letters react to cursor without direct interaction
10
+ - **Font Selection** - Suggest Google Fonts based on natural language descriptions
11
+ - **Font Refinement** - Refine suggestions based on feedback (e.g., "too casual, want gothic")
12
+ - **Dynamic Font Loading** - Automatically load Google Fonts without manual `<link>` tags
10
13
  - **DOM-Based** - Uses CSS transforms (GPU-accelerated), accessible, and styleable
11
14
  - **Modular & Extensible** - Easy to add new animation types and behaviors
12
15
  - **Zero Dependencies** - Vanilla TypeScript/JavaScript
@@ -22,10 +25,18 @@ npm install typography-toolkit
22
25
  ```html
23
26
  <script src="https://unpkg.com/typography-toolkit/dist/typography-toolkit.umd.js"></script>
24
27
  <script>
25
- const { AnimatedText } = TypographyToolkit;
28
+ // Option 1: Use TypographyToolkit namespace
29
+ const text = new TypographyToolkit.AnimatedText({...});
30
+
31
+ // Option 2: Use AnimatedText directly (also available on window)
32
+ const text = new AnimatedText({...});
26
33
  </script>
27
34
  ```
28
35
 
36
+ **Global Names:** When using the UMD build, the library exposes:
37
+ - `window.TypographyToolkit` - Full namespace object
38
+ - `window.AnimatedText` - Direct access to AnimatedText class (for convenience)
39
+
29
40
  ### ES Modules
30
41
  ```javascript
31
42
  import { AnimatedText } from 'typography-toolkit';
@@ -64,7 +75,9 @@ const text = new AnimatedText({
64
75
  style: {
65
76
  fontFamily: 'Arial',
66
77
  fontSize: 24,
67
- color: 'rgba(60, 60, 60, 0.8)'
78
+ color: 'rgba(60, 60, 60, 0.8)',
79
+ textShadow: '2px 2px 4px rgba(0,0,0,0.5)',
80
+ letterSpacing: '2px'
68
81
  }
69
82
  });
70
83
  ```
@@ -148,6 +161,108 @@ interface StyleOptions {
148
161
  fontSize?: number;
149
162
  color?: string;
150
163
  fontWeight?: string;
164
+ textShadow?: string; // CSS text-shadow value
165
+ letterSpacing?: string; // CSS letter-spacing value
166
+ textTransform?: string; // CSS text-transform value
167
+ }
168
+ ```
169
+
170
+ ### Container Styling
171
+
172
+ ```typescript
173
+ containerClass?: string; // CSS class to add to container
174
+ containerStyle?: Record<string, string>; // Inline styles for container
175
+ ```
176
+
177
+ ### Position Constraints
178
+
179
+ ```typescript
180
+ position?: {
181
+ x?: number;
182
+ y?: number;
183
+ constrainToViewport?: boolean; // Keep text within viewport bounds
184
+ }
185
+ ```
186
+
187
+ ### Event Callbacks
188
+
189
+ ```typescript
190
+ callbacks?: {
191
+ onCreate?: (element: HTMLElement) => void; // Called when text is created
192
+ onDestroy?: () => void; // Called when text is destroyed
193
+ onDisintegrate?: (letterIndex: number) => void; // Called when a letter disintegrates
194
+ }
195
+ ```
196
+
197
+ ### Font Selection API
198
+
199
+ #### `suggestFont(description: string): FontSuggestion | undefined`
200
+
201
+ Get a single font suggestion based on a natural language description.
202
+
203
+ ```typescript
204
+ const font = suggestFont('gothic horror');
205
+ // Returns best matching font, or undefined if no matches
206
+ ```
207
+
208
+ #### `suggestFonts(description: string): FontSuggestion[]`
209
+
210
+ Get multiple font suggestions sorted by relevance.
211
+
212
+ ```typescript
213
+ const fonts = suggestFonts('hand-drawn');
214
+ // Returns array of matching fonts, sorted by relevance
215
+ ```
216
+
217
+ #### `loadGoogleFont(fontName: string): Promise<void>`
218
+
219
+ Dynamically load a Google Font. Prevents duplicate loading.
220
+
221
+ ```typescript
222
+ await loadGoogleFont('Caveat');
223
+ // Adds <link> tag to document head if not already loaded
224
+ ```
225
+
226
+ #### `getFontFamily(fontName: string, fallback?: string): string`
227
+
228
+ Get CSS font-family string with appropriate fallback.
229
+
230
+ ```typescript
231
+ getFontFamily('Caveat'); // "'Caveat', cursive"
232
+ getFontFamily('VT323'); // "'VT323', monospace"
233
+ ```
234
+
235
+ #### `refineFont(description: string, feedback: RefinementFeedback): FontSuggestion | undefined`
236
+
237
+ Refine font suggestions based on user feedback using scoring algorithm.
238
+
239
+ ```typescript
240
+ const refined = refineFont('hand-drawn', {
241
+ rejectedFont: 'Caveat',
242
+ negativeAspects: ['too casual'],
243
+ positiveAspects: ['gothic', 'ornate']
244
+ });
245
+ ```
246
+
247
+ #### `refineSuggestion(description: string, feedback: RefinementFeedback): FontSuggestion[]`
248
+
249
+ Get multiple refined suggestions sorted by score.
250
+
251
+ ### Font Suggestion Types
252
+
253
+ ```typescript
254
+ interface FontSuggestion {
255
+ name: string; // Display name
256
+ googleFontsName: string; // Exact Google Fonts name
257
+ categories: string[]; // Tags for matching
258
+ description: string; // Human-readable description
259
+ artistic: boolean; // True if striking/unique (not "safe")
260
+ }
261
+
262
+ interface RefinementFeedback {
263
+ rejectedFont: string; // Font name they didn't like
264
+ negativeAspects: string[]; // What they don't like
265
+ positiveAspects: string[]; // What they want instead
151
266
  }
152
267
  ```
153
268
 
@@ -189,6 +304,56 @@ const text = new AnimatedText({
189
304
  });
190
305
  ```
191
306
 
307
+ ### Font Selection & Loading
308
+
309
+ ```javascript
310
+ import { suggestFont, loadGoogleFont, getFontFamily, AnimatedText } from 'typography-toolkit';
311
+
312
+ // Suggest a font based on description
313
+ const font = suggestFont('hand-drawn');
314
+ // Returns: { name: 'Hand-drawn Casual', googleFontsName: 'Caveat', ... }
315
+
316
+ // Load the font dynamically
317
+ await loadGoogleFont(font.googleFontsName);
318
+
319
+ // Use in animation
320
+ const text = new AnimatedText({
321
+ text: 'HELLO',
322
+ container: document.body,
323
+ style: {
324
+ fontFamily: getFontFamily(font.googleFontsName) // "'Caveat', cursive"
325
+ }
326
+ });
327
+ ```
328
+
329
+ ### Font Refinement with Feedback
330
+
331
+ ```javascript
332
+ import { suggestFont, refineFont, loadGoogleFont, getFontFamily, AnimatedText } from 'typography-toolkit';
333
+
334
+ // Initial suggestion
335
+ const initial = suggestFont('hand-drawn');
336
+ // User doesn't like it: "too casual, want gothic and ornate"
337
+
338
+ // Refine based on feedback
339
+ const refined = refineFont('hand-drawn', {
340
+ rejectedFont: initial.googleFontsName,
341
+ negativeAspects: ['too casual', 'too simple'],
342
+ positiveAspects: ['gothic', 'ornate', 'striking']
343
+ });
344
+ // Returns: { name: 'Gothic Blackletter', googleFontsName: 'UnifrakturMaguntia', ... }
345
+
346
+ // Load and use
347
+ await loadGoogleFont(refined.googleFontsName);
348
+ const text = new AnimatedText({
349
+ text: 'GOTHIC',
350
+ container: document.body,
351
+ style: {
352
+ fontFamily: getFontFamily(refined.googleFontsName)
353
+ }
354
+ });
355
+ ```
356
+
192
357
  ## Performance
193
358
 
194
359
  - Uses `requestAnimationFrame` for smooth 60fps animations
@@ -196,11 +361,113 @@ const text = new AnimatedText({
196
361
  - Efficient proximity calculations
197
362
  - Automatic cleanup on destroy
198
363
 
364
+ ## Common Patterns
365
+
366
+ ### Creating Multiple Texts
367
+
368
+ ```javascript
369
+ const messages = ['HELLO', 'WORLD', 'ANIMATED'];
370
+ messages.forEach((msg, i) => {
371
+ new AnimatedText({
372
+ text: msg,
373
+ container: document.body,
374
+ position: { x: 100 + i * 200, y: 100 },
375
+ fadeOut: 5000
376
+ });
377
+ });
378
+ ```
379
+
380
+ ### Styling with CSS Classes
381
+
382
+ ```javascript
383
+ const text = new AnimatedText({
384
+ text: 'STYLED',
385
+ container: document.body,
386
+ containerClass: 'my-custom-class',
387
+ style: {
388
+ fontSize: 32,
389
+ color: '#ff0000'
390
+ }
391
+ });
392
+
393
+ // CSS:
394
+ // .my-custom-class { background: rgba(0,0,0,0.1); padding: 10px; }
395
+ ```
396
+
397
+ ### Tracking Lifecycle Events
398
+
399
+ ```javascript
400
+ const text = new AnimatedText({
401
+ text: 'TRACKED',
402
+ container: document.body,
403
+ callbacks: {
404
+ onCreate: (element) => {
405
+ console.log('Text created:', element);
406
+ // Analytics tracking, etc.
407
+ },
408
+ onDestroy: () => {
409
+ console.log('Text destroyed');
410
+ },
411
+ onDisintegrate: (letterIndex) => {
412
+ console.log('Letter disintegrated:', letterIndex);
413
+ }
414
+ }
415
+ });
416
+ ```
417
+
418
+ ### Constraining to Viewport
419
+
420
+ ```javascript
421
+ const text = new AnimatedText({
422
+ text: 'CONSTRAINED',
423
+ container: document.body,
424
+ position: {
425
+ x: window.innerWidth - 100, // Might be off-screen
426
+ y: window.innerHeight - 50,
427
+ constrainToViewport: true // Automatically adjusts to stay on screen
428
+ }
429
+ });
430
+ ```
431
+
432
+ ## Troubleshooting
433
+
434
+ ### Text Not Animating?
435
+
436
+ - **Check container**: Ensure the container element exists and is visible
437
+ - **Check animations array**: Make sure at least one animation type is specified
438
+ - **Check browser support**: Requires modern browser with `requestAnimationFrame` support
439
+
440
+ ### Disintegration Not Working?
441
+
442
+ - **Check disintegration.enabled**: Must be set to `true`
443
+ - **Check radius**: Increase `radius` if cursor needs to be closer
444
+ - **Check mouse tracking**: Ensure mouse events are not being blocked
445
+
446
+ ### Text Positioned Off-Screen?
447
+
448
+ - **Use constrainToViewport**: Set `position.constrainToViewport: true`
449
+ - **Check container bounds**: Ensure container has proper dimensions
450
+ - **Manual bounds checking**: Calculate position manually based on container size
451
+
452
+ ### Performance Issues?
453
+
454
+ - **Reduce animation types**: Fewer animations = better performance
455
+ - **Reduce letter count**: Shorter text = better performance
456
+ - **Use fadeOut**: Auto-destroy texts after a delay to prevent memory buildup
457
+ - **Destroy manually**: Call `destroy()` when done with text instances
458
+
459
+ ### Fonts Not Loading?
460
+
461
+ - **Check font name**: Ensure Google Font name is correct
462
+ - **Check network**: Verify Google Fonts CDN is accessible
463
+ - **Check font-family**: Use `getFontFamily()` helper for correct CSS format
464
+
199
465
  ## Browser Support
200
466
 
201
467
  - Modern browsers with ES2020 support
202
468
  - Requires `requestAnimationFrame` API
203
469
  - CSS transforms support
470
+ - Mobile browsers supported (touch events coming soon)
204
471
 
205
472
  ## License
206
473
 
@@ -1,28 +1,28 @@
1
- class y {
2
- update(t, o, s, i = {}) {
3
- const e = i.speed || 1, a = i.amplitude || 20, l = o * 0.2, r = (s * 10 * e + l * 5) % a - a / 2;
1
+ class f {
2
+ update(t, o, e, i = {}) {
3
+ const s = i.speed || 1, c = i.amplitude || 20, g = o * 0.2, l = (e * 10 * s + g * 5) % c - c / 2;
4
4
  return {
5
5
  ...t,
6
- y: r
6
+ y: l
7
7
  };
8
8
  }
9
9
  }
10
- class x {
11
- update(t, o, s, i = {}) {
12
- const e = i.speed || 1, a = i.amplitude || 4, l = o * 0.2, h = Math.sin(s * 0.5 * e + l) * a;
10
+ class C {
11
+ update(t, o, e, i = {}) {
12
+ const s = i.speed || 1, c = i.amplitude || 4, g = o * 0.2, a = Math.sin(e * 0.5 * s + g) * c;
13
13
  return {
14
14
  ...t,
15
- x: h
15
+ x: a
16
16
  };
17
17
  }
18
18
  }
19
- class C {
19
+ class v {
20
20
  constructor() {
21
21
  this.lastUpdate = 0, this.glitchX = 0, this.glitchY = 0, this.glitchRot = 0;
22
22
  }
23
- update(t, o, s, i = {}) {
24
- const e = i.amplitude || 3;
25
- return s - this.lastUpdate > 0.1 && (this.glitchX = (Math.random() - 0.5) * e, this.glitchY = (Math.random() - 0.5) * (e * 0.67), this.glitchRot = (Math.random() - 0.5) * e, this.lastUpdate = s), {
23
+ update(t, o, e, i = {}) {
24
+ const s = i.amplitude || 3;
25
+ return e - this.lastUpdate > 0.1 && (this.glitchX = (Math.random() - 0.5) * s, this.glitchY = (Math.random() - 0.5) * (s * 0.67), this.glitchRot = (Math.random() - 0.5) * s, this.lastUpdate = e), {
26
26
  ...t,
27
27
  x: this.glitchX,
28
28
  y: this.glitchY,
@@ -30,97 +30,109 @@ class C {
30
30
  };
31
31
  }
32
32
  }
33
- class T {
34
- update(t, o, s, i = {}) {
35
- const e = i.speed || 1, a = i.amplitude || 15, l = o * 0.2, r = -((s * 8 * e + l * 4) % a - a / 2);
33
+ class k {
34
+ update(t, o, e, i = {}) {
35
+ const s = i.speed || 1, c = i.amplitude || 15, g = o * 0.2, l = -((e * 8 * s + g * 4) % c - c / 2);
36
36
  return {
37
37
  ...t,
38
- y: r
38
+ y: l
39
39
  };
40
40
  }
41
41
  }
42
- function b(c, t) {
43
- const { x: o, y: s, rotation: i, scale: e, opacity: a } = t;
44
- c.style.transform = `translate(${o}px, ${s}px) rotate(${i}deg) scale(${e})`, c.style.opacity = a.toString();
42
+ function A(n, t) {
43
+ const { x: o, y: e, rotation: i, scale: s, opacity: c } = t;
44
+ n.style.transform = `translate(${o}px, ${e}px) rotate(${i}deg) scale(${s})`, n.style.opacity = c.toString();
45
45
  }
46
- function v(c, t, o, s, i, e) {
47
- if (!e.enabled)
46
+ function b(n, t, o, e, i, s) {
47
+ if (!s.enabled)
48
48
  return {
49
49
  state: { x: 0, y: 0, rotation: 0, scale: 1, opacity: 1 },
50
50
  applied: !1
51
51
  };
52
- const a = e.radius || 80, l = e.strength || 1, h = e.behaviors || ["fall-away", "split-apart", "explode"], r = o - c, d = s - t, m = Math.sqrt(r * r + d * d);
53
- if (m >= a)
52
+ const c = s.radius || 80, g = s.strength || 1, a = s.behaviors || ["fall-away", "split-apart", "explode"], l = o - n, h = e - t, r = Math.sqrt(l * l + h * h);
53
+ if (r >= c)
54
54
  return {
55
55
  state: { x: 0, y: 0, rotation: 0, scale: 1, opacity: 1 },
56
56
  applied: !1
57
57
  };
58
- const n = (1 - m / a) * l, u = Math.atan2(d, r), M = h[i % h.length];
59
- let p;
60
- switch (M) {
58
+ const d = (1 - r / c) * g, p = Math.atan2(h, l), N = a[i % a.length];
59
+ let u;
60
+ switch (N) {
61
61
  case "fall-away":
62
- const w = Math.cos(u) * n * 20, A = Math.sin(u) * n * 40 + n * 30;
63
- p = {
64
- x: w,
65
- y: A,
66
- rotation: n * 15,
62
+ const D = Math.cos(p) * d * 20, L = Math.sin(p) * d * 40 + d * 30;
63
+ u = {
64
+ x: D,
65
+ y: L,
66
+ rotation: d * 15,
67
67
  scale: 1,
68
- opacity: 1 - n * 0.6
68
+ opacity: 1 - d * 0.6
69
69
  };
70
70
  break;
71
71
  case "split-apart":
72
- const f = i % 2 === 0 ? 1 : -1;
73
- p = {
74
- x: f * n * 50,
75
- y: (Math.random() - 0.5) * n * 10,
76
- rotation: n * 10 * f,
72
+ const w = i % 2 === 0 ? 1 : -1;
73
+ u = {
74
+ x: w * d * 50,
75
+ y: (Math.random() - 0.5) * d * 10,
76
+ rotation: d * 10 * w,
77
77
  scale: 1,
78
- opacity: 1 - n * 0.6
78
+ opacity: 1 - d * 0.6
79
79
  };
80
80
  break;
81
81
  case "explode":
82
- const g = u + (Math.random() - 0.5) * 0.5;
83
- p = {
84
- x: Math.cos(g) * n * 40,
85
- y: Math.sin(g) * n * 40,
86
- rotation: n * 30,
87
- scale: 1 + n * 0.4,
88
- opacity: 1 - n * 0.6
82
+ const F = p + (Math.random() - 0.5) * 0.5;
83
+ u = {
84
+ x: Math.cos(F) * d * 40,
85
+ y: Math.sin(F) * d * 40,
86
+ rotation: d * 30,
87
+ scale: 1 + d * 0.4,
88
+ opacity: 1 - d * 0.6
89
89
  };
90
90
  break;
91
91
  default:
92
- p = { x: 0, y: 0, rotation: 0, scale: 1, opacity: 1 };
92
+ u = { x: 0, y: 0, rotation: 0, scale: 1, opacity: 1 };
93
93
  }
94
94
  return {
95
- state: p,
95
+ state: u,
96
96
  applied: !0
97
97
  };
98
98
  }
99
- class F {
99
+ class x {
100
100
  constructor(t) {
101
- this.letters = [], this.animationFrame = null, this.mouseX = 0, this.mouseY = 0, this.mouseMoveHandler = null, this.startTime = Date.now(), this.isDestroyed = !1, this.container = t.container, this.animationTypes = t.animations || ["falling", "splitting", "glitching", "floating"], this.cycle = t.cycle !== !1, this.speed = t.speed || 1, this.amplitude = t.amplitude || 1, this.disintegration = t.disintegration || { enabled: !1 }, this.style = t.style || {}, this.fadeOut = t.fadeOut || 0, this.textContainer = document.createElement("div"), this.textContainer.style.position = "absolute", this.textContainer.style.pointerEvents = "none", this.textContainer.style.zIndex = "1", t.position && (t.position.x !== void 0 && (this.textContainer.style.left = `${t.position.x}px`), t.position.y !== void 0 && (this.textContainer.style.top = `${t.position.y}px`)), this.createLetters(t.text), this.setupMouseTracking(), this.startAnimation(), this.fadeOut > 0 && setTimeout(() => this.destroy(), this.fadeOut);
101
+ var o;
102
+ if (this.letters = [], this.animationFrame = null, this.mouseX = 0, this.mouseY = 0, this.mouseMoveHandler = null, this.startTime = Date.now(), this.isDestroyed = !1, this.container = t.container, this.animationTypes = t.animations || ["falling", "splitting", "glitching", "floating"], this.cycle = t.cycle !== !1, this.speed = t.speed || 1, this.amplitude = t.amplitude || 1, this.disintegration = t.disintegration || { enabled: !1 }, this.style = t.style || {}, this.fadeOut = t.fadeOut || 0, this.callbacks = t.callbacks, this.textContainer = document.createElement("div"), this.textContainer.style.position = "absolute", this.textContainer.style.pointerEvents = "none", this.textContainer.style.zIndex = "1", t.containerClass && (this.textContainer.className = t.containerClass), t.containerStyle && Object.entries(t.containerStyle).forEach(([e, i]) => {
103
+ this.textContainer.style.setProperty(e, i);
104
+ }), t.position) {
105
+ let e = t.position.x, i = t.position.y;
106
+ if (t.position.constrainToViewport) {
107
+ this.container.getBoundingClientRect();
108
+ const s = window.innerWidth, c = window.innerHeight, g = t.text.length * 20;
109
+ e !== void 0 && (e = Math.max(0, Math.min(e, s - g))), i !== void 0 && (i = Math.max(0, Math.min(i, c - 50)));
110
+ }
111
+ e !== void 0 && (this.textContainer.style.left = `${e}px`), i !== void 0 && (this.textContainer.style.top = `${i}px`);
112
+ }
113
+ this.createLetters(t.text), this.setupMouseTracking(), this.startAnimation(), (o = this.callbacks) != null && o.onCreate && this.callbacks.onCreate(this.textContainer), this.fadeOut > 0 && setTimeout(() => this.destroy(), this.fadeOut);
102
114
  }
103
115
  createLetters(t) {
104
- t.toUpperCase().split("").forEach((s, i) => {
105
- if (s === " ") {
106
- const l = document.createTextNode(" ");
107
- this.textContainer.appendChild(l);
116
+ t.toUpperCase().split("").forEach((e, i) => {
117
+ if (e === " ") {
118
+ const g = document.createTextNode(" ");
119
+ this.textContainer.appendChild(g);
108
120
  return;
109
121
  }
110
- const e = document.createElement("span");
111
- e.className = "animated-letter", e.textContent = s, e.dataset.index = i.toString(), e.dataset.char = s, this.applyStyle(e), e.style.display = "inline-block", e.style.position = "relative", e.style.transition = "transform 0.1s ease-out", this.textContainer.appendChild(e);
112
- const a = e.getBoundingClientRect();
122
+ const s = document.createElement("span");
123
+ s.className = "animated-letter", s.textContent = e, s.dataset.index = i.toString(), s.dataset.char = e, this.applyStyle(s), s.style.display = "inline-block", s.style.position = "relative", s.style.transition = "transform 0.1s ease-out", this.textContainer.appendChild(s);
124
+ const c = s.getBoundingClientRect();
113
125
  this.letters.push({
114
- element: e,
126
+ element: s,
115
127
  index: i,
116
- char: s,
117
- baseX: a.left,
118
- baseY: a.top
128
+ char: e,
129
+ baseX: c.left,
130
+ baseY: c.top
119
131
  });
120
132
  }), this.container.appendChild(this.textContainer);
121
133
  }
122
134
  applyStyle(t) {
123
- this.style.fontFamily && (t.style.fontFamily = this.style.fontFamily), this.style.fontSize && (t.style.fontSize = `${this.style.fontSize}px`), this.style.color && (t.style.color = this.style.color), this.style.fontWeight && (t.style.fontWeight = this.style.fontWeight);
135
+ this.style.fontFamily && (t.style.fontFamily = this.style.fontFamily), this.style.fontSize && (t.style.fontSize = `${this.style.fontSize}px`), this.style.color && (t.style.color = this.style.color), this.style.fontWeight && (t.style.fontWeight = this.style.fontWeight), this.style.textShadow && (t.style.textShadow = this.style.textShadow), this.style.letterSpacing && (t.style.letterSpacing = this.style.letterSpacing), this.style.textTransform && (t.style.textTransform = this.style.textTransform);
124
136
  }
125
137
  setupMouseTracking() {
126
138
  this.disintegration.enabled && (this.mouseMoveHandler = (t) => {
@@ -131,31 +143,32 @@ class F {
131
143
  const t = () => {
132
144
  if (this.isDestroyed) return;
133
145
  const o = (Date.now() - this.startTime) * 1e-3;
134
- this.letters.forEach((s, i) => {
135
- const e = s.element.getBoundingClientRect(), a = e.left + e.width / 2, l = e.top + e.height / 2, h = v(
136
- a,
137
- l,
146
+ this.letters.forEach((e, i) => {
147
+ var h;
148
+ const s = e.element.getBoundingClientRect(), c = s.left + s.width / 2, g = s.top + s.height / 2, a = b(
149
+ c,
150
+ g,
138
151
  this.mouseX,
139
152
  this.mouseY,
140
153
  i,
141
154
  this.disintegration
142
155
  );
143
- let r;
144
- if (h.applied)
145
- r = h.state;
156
+ let l;
157
+ if (a.applied)
158
+ l = a.state, (h = this.callbacks) != null && h.onDisintegrate && this.callbacks.onDisintegrate(i);
146
159
  else {
147
- const d = this.getAnimationType(i);
148
- r = this.getAnimation(d).update(
160
+ const r = this.getAnimationType(i);
161
+ l = this.getAnimation(r).update(
149
162
  { x: 0, y: 0, rotation: 0, scale: 1, opacity: 1 },
150
163
  i,
151
164
  o,
152
165
  {
153
166
  speed: this.speed,
154
- amplitude: this.amplitude * (d === "falling" || d === "floating" ? 20 : 4)
167
+ amplitude: this.amplitude * (r === "falling" || r === "floating" ? 20 : 4)
155
168
  }
156
169
  );
157
170
  }
158
- b(s.element, r);
171
+ A(e.element, l);
159
172
  }), this.animationFrame = requestAnimationFrame(t);
160
173
  };
161
174
  this.animationFrame = requestAnimationFrame(t);
@@ -166,22 +179,23 @@ class F {
166
179
  getAnimation(t) {
167
180
  switch (t) {
168
181
  case "falling":
169
- return new y();
182
+ return new f();
170
183
  case "splitting":
171
- return new x();
172
- case "glitching":
173
184
  return new C();
185
+ case "glitching":
186
+ return new v();
174
187
  case "floating":
175
- return new T();
188
+ return new k();
176
189
  default:
177
- return new y();
190
+ return new f();
178
191
  }
179
192
  }
180
193
  /**
181
194
  * Destroy the animated text instance
182
195
  */
183
196
  destroy() {
184
- this.isDestroyed || (this.isDestroyed = !0, this.animationFrame !== null && cancelAnimationFrame(this.animationFrame), this.mouseMoveHandler && document.removeEventListener("mousemove", this.mouseMoveHandler), this.textContainer.style.transition = "opacity 3s ease", this.textContainer.style.opacity = "0", setTimeout(() => {
197
+ var t;
198
+ this.isDestroyed || (this.isDestroyed = !0, this.animationFrame !== null && cancelAnimationFrame(this.animationFrame), this.mouseMoveHandler && document.removeEventListener("mousemove", this.mouseMoveHandler), (t = this.callbacks) != null && t.onDestroy && this.callbacks.onDestroy(), this.textContainer.style.transition = "opacity 3s ease", this.textContainer.style.opacity = "0", setTimeout(() => {
185
199
  this.textContainer.parentNode && this.textContainer.parentNode.removeChild(this.textContainer);
186
200
  }, 3e3));
187
201
  }
@@ -192,19 +206,281 @@ class F {
192
206
  return this.textContainer;
193
207
  }
194
208
  }
195
- typeof window < "u" && (window.TypographyToolkit = {
196
- AnimatedText: F,
197
- FallingAnimation: y,
198
- SplittingAnimation: x,
199
- GlitchingAnimation: C,
200
- FloatingAnimation: T,
201
- calculateDisintegration: v
202
- });
209
+ const S = [
210
+ // Hand-drawn / Handwriting
211
+ {
212
+ name: "Hand-drawn Casual",
213
+ googleFontsName: "Caveat",
214
+ categories: ["hand-drawn", "handwriting", "casual", "sketchy", "informal"],
215
+ description: "Casual handwritten style, friendly and approachable",
216
+ artistic: !1
217
+ },
218
+ {
219
+ name: "Hand-drawn Playful",
220
+ googleFontsName: "Finger Paint",
221
+ categories: ["hand-drawn", "playful", "childlike", "casual", "sketchy"],
222
+ description: "Bold hand-drawn style, playful and energetic",
223
+ artistic: !0
224
+ },
225
+ {
226
+ name: "Hand-drawn Script",
227
+ googleFontsName: "Dancing Script",
228
+ categories: ["hand-drawn", "script", "elegant", "flowing", "cursive"],
229
+ description: "Elegant flowing script, graceful and organic",
230
+ artistic: !1
231
+ },
232
+ // Gothic / Medieval
233
+ {
234
+ name: "Gothic Blackletter",
235
+ googleFontsName: "UnifrakturMaguntia",
236
+ categories: ["gothic", "medieval", "blackletter", "ornate", "historical"],
237
+ description: "Medieval blackletter style, ornate and historical",
238
+ artistic: !0
239
+ },
240
+ {
241
+ name: "Gothic Horror",
242
+ googleFontsName: "Creepster",
243
+ categories: ["gothic", "horror", "creepy", "dripping", "display", "striking"],
244
+ description: "Horror-style font with dripping effects, very striking",
245
+ artistic: !0
246
+ },
247
+ {
248
+ name: "Gothic Bold",
249
+ googleFontsName: "Eater",
250
+ categories: ["gothic", "bold", "aggressive", "display", "striking"],
251
+ description: "Bold aggressive display font, powerful impact",
252
+ artistic: !0
253
+ },
254
+ // Futuristic / Sci-fi
255
+ {
256
+ name: "Futuristic Digital",
257
+ googleFontsName: "Orbitron",
258
+ categories: ["futuristic", "sci-fi", "digital", "tech", "modern", "geometric"],
259
+ description: "Futuristic geometric font, tech-forward and modern",
260
+ artistic: !0
261
+ },
262
+ {
263
+ name: "Futuristic Display",
264
+ googleFontsName: "Bungee",
265
+ categories: ["futuristic", "display", "bold", "condensed", "striking"],
266
+ description: "Bold condensed display font, high impact",
267
+ artistic: !0
268
+ },
269
+ {
270
+ name: "Futuristic Outline",
271
+ googleFontsName: "Bungee Shade",
272
+ categories: ["futuristic", "outline", "display", "bold", "striking"],
273
+ description: "Outlined version of Bungee, bold and striking",
274
+ artistic: !0
275
+ },
276
+ // Retro / Vintage
277
+ {
278
+ name: "Retro Terminal",
279
+ googleFontsName: "VT323",
280
+ categories: ["retro", "terminal", "monospace", "pixel", "80s", "tech"],
281
+ description: "Retro terminal font, pixelated and nostalgic",
282
+ artistic: !0
283
+ },
284
+ {
285
+ name: "Retro Pixel",
286
+ googleFontsName: "Press Start 2P",
287
+ categories: ["retro", "pixel", "8-bit", "arcade", "nostalgic", "display"],
288
+ description: "8-bit pixel font, classic arcade style",
289
+ artistic: !0
290
+ },
291
+ {
292
+ name: "Retro Display",
293
+ googleFontsName: "Frijole",
294
+ categories: ["retro", "playful", "rounded", "display", "casual"],
295
+ description: "Playful rounded retro font, fun and casual",
296
+ artistic: !0
297
+ },
298
+ // Decorative / Ornate
299
+ {
300
+ name: "Decorative Ornate",
301
+ googleFontsName: "Fascinate",
302
+ categories: ["decorative", "ornate", "display", "striking", "elaborate"],
303
+ description: "Highly decorative display font, ornate and elaborate",
304
+ artistic: !0
305
+ },
306
+ {
307
+ name: "Decorative Outline",
308
+ googleFontsName: "Fascinate Inline",
309
+ categories: ["decorative", "outline", "display", "ornate", "striking"],
310
+ description: "Outlined decorative font, ornate and striking",
311
+ artistic: !0
312
+ },
313
+ {
314
+ name: "Decorative Script",
315
+ googleFontsName: "Fredericka the Great",
316
+ categories: ["decorative", "script", "ornate", "elegant", "display"],
317
+ description: "Elegant decorative script, ornate and sophisticated",
318
+ artistic: !0
319
+ },
320
+ // Creepy / Horror
321
+ {
322
+ name: "Horror Dripping",
323
+ googleFontsName: "Nosifer",
324
+ categories: ["horror", "creepy", "dripping", "blood", "striking", "display"],
325
+ description: "Creepy font with blood-dripping effects, very striking",
326
+ artistic: !0
327
+ },
328
+ // Tech / Monospace
329
+ {
330
+ name: "Tech Monospace",
331
+ googleFontsName: "Share Tech Mono",
332
+ categories: ["tech", "monospace", "terminal", "code", "modern"],
333
+ description: "Clean tech monospace font, modern and readable",
334
+ artistic: !1
335
+ },
336
+ {
337
+ name: "Tech Display",
338
+ googleFontsName: "Rajdhani",
339
+ categories: ["tech", "modern", "geometric", "sans-serif", "futuristic"],
340
+ description: "Modern geometric tech font, clean and futuristic",
341
+ artistic: !1
342
+ },
343
+ // Organic / Natural
344
+ {
345
+ name: "Organic Flowing",
346
+ googleFontsName: "Dancing Script",
347
+ categories: ["organic", "flowing", "natural", "script", "elegant"],
348
+ description: "Flowing organic script, natural and elegant",
349
+ artistic: !1
350
+ },
351
+ // Modern / Clean
352
+ {
353
+ name: "Modern Sans",
354
+ googleFontsName: "Roboto",
355
+ categories: ["modern", "clean", "sans-serif", "readable", "professional"],
356
+ description: "Clean modern sans-serif, professional and readable",
357
+ artistic: !1
358
+ },
359
+ {
360
+ name: "Modern Serif",
361
+ googleFontsName: "Playfair Display",
362
+ categories: ["modern", "serif", "elegant", "sophisticated", "readable"],
363
+ description: "Elegant modern serif, sophisticated and readable",
364
+ artistic: !1
365
+ },
366
+ // Bold / Display
367
+ {
368
+ name: "Bold Condensed",
369
+ googleFontsName: "Bungee",
370
+ categories: ["bold", "condensed", "display", "striking", "impact"],
371
+ description: "Bold condensed display font, high impact",
372
+ artistic: !0
373
+ },
374
+ {
375
+ name: "Bold Aggressive",
376
+ googleFontsName: "Eater",
377
+ categories: ["bold", "aggressive", "display", "striking", "powerful"],
378
+ description: "Bold aggressive display font, powerful and striking",
379
+ artistic: !0
380
+ }
381
+ ];
382
+ function y(n) {
383
+ const t = n.toLowerCase();
384
+ return S.map((e) => {
385
+ let i = 0;
386
+ for (const s of e.categories)
387
+ t.includes(s) && (i += 10);
388
+ return (e.name.toLowerCase().includes(t) || t.includes(e.name.toLowerCase())) && (i += 15), (e.description.toLowerCase().includes(t) || t.includes(e.description.toLowerCase())) && (i += 8), e.artistic && (i += 1), { font: e, score: i };
389
+ }).filter((e) => e.score > 0).sort((e, i) => i.score - e.score).map((e) => e.font);
390
+ }
391
+ function E(n) {
392
+ return y(n)[0];
393
+ }
394
+ const m = /* @__PURE__ */ new Set();
395
+ function T(n) {
396
+ return new Promise((t, o) => {
397
+ const e = n.replace(/\s+/g, "+");
398
+ if (m.has(e)) {
399
+ t();
400
+ return;
401
+ }
402
+ if (document.querySelector(
403
+ `link[href*="fonts.googleapis.com"][href*="${e}"]`
404
+ )) {
405
+ m.add(e), t();
406
+ return;
407
+ }
408
+ const s = document.createElement("link");
409
+ s.rel = "stylesheet", s.href = `https://fonts.googleapis.com/css2?family=${e}:wght@400&display=swap`, s.onload = () => {
410
+ m.add(e), t();
411
+ }, s.onerror = () => {
412
+ console.warn(`Failed to load Google Font: ${n}`), o(new Error(`Failed to load Google Font: ${n}`));
413
+ }, document.head.appendChild(s);
414
+ });
415
+ }
416
+ function B(n) {
417
+ return Promise.all(n.map((t) => T(t)));
418
+ }
419
+ function P(n) {
420
+ const t = n.replace(/\s+/g, "+");
421
+ return m.has(t);
422
+ }
423
+ function R(n, t = "sans-serif") {
424
+ const o = ["Caveat", "Dancing Script", "Finger Paint", "Fredericka the Great"], e = ["VT323", "Press Start 2P", "Share Tech Mono"];
425
+ let i = t;
426
+ return o.includes(n) ? i = "cursive" : e.includes(n) && (i = "monospace"), `'${n}', ${i}`;
427
+ }
428
+ function M(n, t) {
429
+ const o = n.toLowerCase(), e = t.rejectedFont.toLowerCase(), i = t.negativeAspects.map((a) => a.toLowerCase()), s = t.positiveAspects.map((a) => a.toLowerCase());
430
+ return y(n).filter(
431
+ (a) => a.googleFontsName.toLowerCase() !== e && a.name.toLowerCase() !== e
432
+ ).map((a) => {
433
+ let l = 0;
434
+ for (const r of a.categories)
435
+ o.includes(r) && (l += 5);
436
+ for (const r of s)
437
+ (a.categories.some((p) => p.includes(r) || r.includes(p)) || a.name.toLowerCase().includes(r) || a.description.toLowerCase().includes(r)) && (l += 15);
438
+ for (const r of i)
439
+ (a.categories.some((p) => p.includes(r) || r.includes(p)) || a.name.toLowerCase().includes(r) || a.description.toLowerCase().includes(r)) && (l -= 20);
440
+ const h = s.some(
441
+ (r) => r.includes("striking") || r.includes("artistic") || r.includes("unique")
442
+ );
443
+ return h && a.artistic && (l += 10), h && !a.artistic && (l -= 5), { font: a, score: l };
444
+ }).filter((a) => a.score > 0).sort((a, l) => l.score - a.score).map((a) => a.font);
445
+ }
446
+ function H(n, t) {
447
+ return M(n, t)[0];
448
+ }
449
+ if (typeof window < "u") {
450
+ const n = {
451
+ AnimatedText: x,
452
+ FallingAnimation: f,
453
+ SplittingAnimation: C,
454
+ GlitchingAnimation: v,
455
+ FloatingAnimation: k,
456
+ calculateDisintegration: b,
457
+ // Font utilities
458
+ fontSuggestions: S,
459
+ suggestFonts: y,
460
+ suggestFont: E,
461
+ loadGoogleFont: T,
462
+ loadGoogleFonts: B,
463
+ isFontLoaded: P,
464
+ getFontFamily: R,
465
+ refineSuggestion: M,
466
+ refineFont: H
467
+ };
468
+ window.TypographyToolkit = n, window.AnimatedText = x;
469
+ }
203
470
  export {
204
- F as AnimatedText,
205
- y as FallingAnimation,
206
- T as FloatingAnimation,
207
- C as GlitchingAnimation,
208
- x as SplittingAnimation,
209
- v as calculateDisintegration
471
+ x as AnimatedText,
472
+ f as FallingAnimation,
473
+ k as FloatingAnimation,
474
+ v as GlitchingAnimation,
475
+ C as SplittingAnimation,
476
+ b as calculateDisintegration,
477
+ S as fontSuggestions,
478
+ R as getFontFamily,
479
+ P as isFontLoaded,
480
+ T as loadGoogleFont,
481
+ B as loadGoogleFonts,
482
+ H as refineFont,
483
+ M as refineSuggestion,
484
+ E as suggestFont,
485
+ y as suggestFonts
210
486
  };
@@ -1 +1 @@
1
- var TypographyToolkit=function(d){"use strict";class u{update(t,o,n,i={}){const e=i.speed||1,s=i.amplitude||20,l=o*.2,r=(n*10*e+l*5)%s-s/2;return{...t,y:r}}}class y{update(t,o,n,i={}){const e=i.speed||1,s=i.amplitude||4,l=o*.2,h=Math.sin(n*.5*e+l)*s;return{...t,x:h}}}class f{constructor(){this.lastUpdate=0,this.glitchX=0,this.glitchY=0,this.glitchRot=0}update(t,o,n,i={}){const e=i.amplitude||3;return n-this.lastUpdate>.1&&(this.glitchX=(Math.random()-.5)*e,this.glitchY=(Math.random()-.5)*(e*.67),this.glitchRot=(Math.random()-.5)*e,this.lastUpdate=n),{...t,x:this.glitchX,y:this.glitchY,rotation:this.glitchRot}}}class g{update(t,o,n,i={}){const e=i.speed||1,s=i.amplitude||15,l=o*.2,r=-((n*8*e+l*4)%s-s/2);return{...t,y:r}}}function w(c,t){const{x:o,y:n,rotation:i,scale:e,opacity:s}=t;c.style.transform=`translate(${o}px, ${n}px) rotate(${i}deg) scale(${e})`,c.style.opacity=s.toString()}function x(c,t,o,n,i,e){if(!e.enabled)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const s=e.radius||80,l=e.strength||1,h=e.behaviors||["fall-away","split-apart","explode"],r=o-c,m=n-t,C=Math.sqrt(r*r+m*m);if(C>=s)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const a=(1-C/s)*l,T=Math.atan2(m,r),b=h[i%h.length];let p;switch(b){case"fall-away":const F=Math.cos(T)*a*20,S=Math.sin(T)*a*40+a*30;p={x:F,y:S,rotation:a*15,scale:1,opacity:1-a*.6};break;case"split-apart":const A=i%2===0?1:-1;p={x:A*a*50,y:(Math.random()-.5)*a*10,rotation:a*10*A,scale:1,opacity:1-a*.6};break;case"explode":const M=T+(Math.random()-.5)*.5;p={x:Math.cos(M)*a*40,y:Math.sin(M)*a*40,rotation:a*30,scale:1+a*.4,opacity:1-a*.6};break;default:p={x:0,y:0,rotation:0,scale:1,opacity:1}}return{state:p,applied:!0}}class v{constructor(t){this.letters=[],this.animationFrame=null,this.mouseX=0,this.mouseY=0,this.mouseMoveHandler=null,this.startTime=Date.now(),this.isDestroyed=!1,this.container=t.container,this.animationTypes=t.animations||["falling","splitting","glitching","floating"],this.cycle=t.cycle!==!1,this.speed=t.speed||1,this.amplitude=t.amplitude||1,this.disintegration=t.disintegration||{enabled:!1},this.style=t.style||{},this.fadeOut=t.fadeOut||0,this.textContainer=document.createElement("div"),this.textContainer.style.position="absolute",this.textContainer.style.pointerEvents="none",this.textContainer.style.zIndex="1",t.position&&(t.position.x!==void 0&&(this.textContainer.style.left=`${t.position.x}px`),t.position.y!==void 0&&(this.textContainer.style.top=`${t.position.y}px`)),this.createLetters(t.text),this.setupMouseTracking(),this.startAnimation(),this.fadeOut>0&&setTimeout(()=>this.destroy(),this.fadeOut)}createLetters(t){t.toUpperCase().split("").forEach((n,i)=>{if(n===" "){const l=document.createTextNode(" ");this.textContainer.appendChild(l);return}const e=document.createElement("span");e.className="animated-letter",e.textContent=n,e.dataset.index=i.toString(),e.dataset.char=n,this.applyStyle(e),e.style.display="inline-block",e.style.position="relative",e.style.transition="transform 0.1s ease-out",this.textContainer.appendChild(e);const s=e.getBoundingClientRect();this.letters.push({element:e,index:i,char:n,baseX:s.left,baseY:s.top})}),this.container.appendChild(this.textContainer)}applyStyle(t){this.style.fontFamily&&(t.style.fontFamily=this.style.fontFamily),this.style.fontSize&&(t.style.fontSize=`${this.style.fontSize}px`),this.style.color&&(t.style.color=this.style.color),this.style.fontWeight&&(t.style.fontWeight=this.style.fontWeight)}setupMouseTracking(){this.disintegration.enabled&&(this.mouseMoveHandler=t=>{this.mouseX=t.clientX,this.mouseY=t.clientY},document.addEventListener("mousemove",this.mouseMoveHandler))}startAnimation(){const t=()=>{if(this.isDestroyed)return;const o=(Date.now()-this.startTime)*.001;this.letters.forEach((n,i)=>{const e=n.element.getBoundingClientRect(),s=e.left+e.width/2,l=e.top+e.height/2,h=x(s,l,this.mouseX,this.mouseY,i,this.disintegration);let r;if(h.applied)r=h.state;else{const m=this.getAnimationType(i);r=this.getAnimation(m).update({x:0,y:0,rotation:0,scale:1,opacity:1},i,o,{speed:this.speed,amplitude:this.amplitude*(m==="falling"||m==="floating"?20:4)})}w(n.element,r)}),this.animationFrame=requestAnimationFrame(t)};this.animationFrame=requestAnimationFrame(t)}getAnimationType(t){return this.cycle?this.animationTypes[t%this.animationTypes.length]:this.animationTypes[0]}getAnimation(t){switch(t){case"falling":return new u;case"splitting":return new y;case"glitching":return new f;case"floating":return new g;default:return new u}}destroy(){this.isDestroyed||(this.isDestroyed=!0,this.animationFrame!==null&&cancelAnimationFrame(this.animationFrame),this.mouseMoveHandler&&document.removeEventListener("mousemove",this.mouseMoveHandler),this.textContainer.style.transition="opacity 3s ease",this.textContainer.style.opacity="0",setTimeout(()=>{this.textContainer.parentNode&&this.textContainer.parentNode.removeChild(this.textContainer)},3e3))}getElement(){return this.textContainer}}return typeof window<"u"&&(window.TypographyToolkit={AnimatedText:v,FallingAnimation:u,SplittingAnimation:y,GlitchingAnimation:f,FloatingAnimation:g,calculateDisintegration:x}),d.AnimatedText=v,d.FallingAnimation=u,d.FloatingAnimation=g,d.GlitchingAnimation=f,d.SplittingAnimation=y,d.calculateDisintegration=x,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}),d}({});
1
+ var TypographyToolkit=function(d){"use strict";class f{update(t,o,e,i={}){const s=i.speed||1,l=i.amplitude||20,h=o*.2,c=(e*10*s+h*5)%l-l/2;return{...t,y:c}}}class w{update(t,o,e,i={}){const s=i.speed||1,l=i.amplitude||4,h=o*.2,a=Math.sin(e*.5*s+h)*l;return{...t,x:a}}}class C{constructor(){this.lastUpdate=0,this.glitchX=0,this.glitchY=0,this.glitchRot=0}update(t,o,e,i={}){const s=i.amplitude||3;return e-this.lastUpdate>.1&&(this.glitchX=(Math.random()-.5)*s,this.glitchY=(Math.random()-.5)*(s*.67),this.glitchRot=(Math.random()-.5)*s,this.lastUpdate=e),{...t,x:this.glitchX,y:this.glitchY,rotation:this.glitchRot}}}class v{update(t,o,e,i={}){const s=i.speed||1,l=i.amplitude||15,h=o*.2,c=-((e*8*s+h*4)%l-l/2);return{...t,y:c}}}function B(n,t){const{x:o,y:e,rotation:i,scale:s,opacity:l}=t;n.style.transform=`translate(${o}px, ${e}px) rotate(${i}deg) scale(${s})`,n.style.opacity=l.toString()}function k(n,t,o,e,i,s){if(!s.enabled)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const l=s.radius||80,h=s.strength||1,a=s.behaviors||["fall-away","split-apart","explode"],c=o-n,u=e-t,r=Math.sqrt(c*c+u*u);if(r>=l)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const g=(1-r/l)*h,m=Math.atan2(u,c),R=a[i%a.length];let p;switch(R){case"fall-away":const H=Math.cos(m)*g*20,G=Math.sin(m)*g*40+g*30;p={x:H,y:G,rotation:g*15,scale:1,opacity:1-g*.6};break;case"split-apart":const E=i%2===0?1:-1;p={x:E*g*50,y:(Math.random()-.5)*g*10,rotation:g*10*E,scale:1,opacity:1-g*.6};break;case"explode":const P=m+(Math.random()-.5)*.5;p={x:Math.cos(P)*g*40,y:Math.sin(P)*g*40,rotation:g*30,scale:1+g*.4,opacity:1-g*.6};break;default:p={x:0,y:0,rotation:0,scale:1,opacity:1}}return{state:p,applied:!0}}class b{constructor(t){var o;if(this.letters=[],this.animationFrame=null,this.mouseX=0,this.mouseY=0,this.mouseMoveHandler=null,this.startTime=Date.now(),this.isDestroyed=!1,this.container=t.container,this.animationTypes=t.animations||["falling","splitting","glitching","floating"],this.cycle=t.cycle!==!1,this.speed=t.speed||1,this.amplitude=t.amplitude||1,this.disintegration=t.disintegration||{enabled:!1},this.style=t.style||{},this.fadeOut=t.fadeOut||0,this.callbacks=t.callbacks,this.textContainer=document.createElement("div"),this.textContainer.style.position="absolute",this.textContainer.style.pointerEvents="none",this.textContainer.style.zIndex="1",t.containerClass&&(this.textContainer.className=t.containerClass),t.containerStyle&&Object.entries(t.containerStyle).forEach(([e,i])=>{this.textContainer.style.setProperty(e,i)}),t.position){let e=t.position.x,i=t.position.y;if(t.position.constrainToViewport){this.container.getBoundingClientRect();const s=window.innerWidth,l=window.innerHeight,h=t.text.length*20;e!==void 0&&(e=Math.max(0,Math.min(e,s-h))),i!==void 0&&(i=Math.max(0,Math.min(i,l-50)))}e!==void 0&&(this.textContainer.style.left=`${e}px`),i!==void 0&&(this.textContainer.style.top=`${i}px`)}this.createLetters(t.text),this.setupMouseTracking(),this.startAnimation(),(o=this.callbacks)!=null&&o.onCreate&&this.callbacks.onCreate(this.textContainer),this.fadeOut>0&&setTimeout(()=>this.destroy(),this.fadeOut)}createLetters(t){t.toUpperCase().split("").forEach((e,i)=>{if(e===" "){const h=document.createTextNode(" ");this.textContainer.appendChild(h);return}const s=document.createElement("span");s.className="animated-letter",s.textContent=e,s.dataset.index=i.toString(),s.dataset.char=e,this.applyStyle(s),s.style.display="inline-block",s.style.position="relative",s.style.transition="transform 0.1s ease-out",this.textContainer.appendChild(s);const l=s.getBoundingClientRect();this.letters.push({element:s,index:i,char:e,baseX:l.left,baseY:l.top})}),this.container.appendChild(this.textContainer)}applyStyle(t){this.style.fontFamily&&(t.style.fontFamily=this.style.fontFamily),this.style.fontSize&&(t.style.fontSize=`${this.style.fontSize}px`),this.style.color&&(t.style.color=this.style.color),this.style.fontWeight&&(t.style.fontWeight=this.style.fontWeight),this.style.textShadow&&(t.style.textShadow=this.style.textShadow),this.style.letterSpacing&&(t.style.letterSpacing=this.style.letterSpacing),this.style.textTransform&&(t.style.textTransform=this.style.textTransform)}setupMouseTracking(){this.disintegration.enabled&&(this.mouseMoveHandler=t=>{this.mouseX=t.clientX,this.mouseY=t.clientY},document.addEventListener("mousemove",this.mouseMoveHandler))}startAnimation(){const t=()=>{if(this.isDestroyed)return;const o=(Date.now()-this.startTime)*.001;this.letters.forEach((e,i)=>{var u;const s=e.element.getBoundingClientRect(),l=s.left+s.width/2,h=s.top+s.height/2,a=k(l,h,this.mouseX,this.mouseY,i,this.disintegration);let c;if(a.applied)c=a.state,(u=this.callbacks)!=null&&u.onDisintegrate&&this.callbacks.onDisintegrate(i);else{const r=this.getAnimationType(i);c=this.getAnimation(r).update({x:0,y:0,rotation:0,scale:1,opacity:1},i,o,{speed:this.speed,amplitude:this.amplitude*(r==="falling"||r==="floating"?20:4)})}B(e.element,c)}),this.animationFrame=requestAnimationFrame(t)};this.animationFrame=requestAnimationFrame(t)}getAnimationType(t){return this.cycle?this.animationTypes[t%this.animationTypes.length]:this.animationTypes[0]}getAnimation(t){switch(t){case"falling":return new f;case"splitting":return new w;case"glitching":return new C;case"floating":return new v;default:return new f}}destroy(){var t;this.isDestroyed||(this.isDestroyed=!0,this.animationFrame!==null&&cancelAnimationFrame(this.animationFrame),this.mouseMoveHandler&&document.removeEventListener("mousemove",this.mouseMoveHandler),(t=this.callbacks)!=null&&t.onDestroy&&this.callbacks.onDestroy(),this.textContainer.style.transition="opacity 3s ease",this.textContainer.style.opacity="0",setTimeout(()=>{this.textContainer.parentNode&&this.textContainer.parentNode.removeChild(this.textContainer)},3e3))}getElement(){return this.textContainer}}const x=[{name:"Hand-drawn Casual",googleFontsName:"Caveat",categories:["hand-drawn","handwriting","casual","sketchy","informal"],description:"Casual handwritten style, friendly and approachable",artistic:!1},{name:"Hand-drawn Playful",googleFontsName:"Finger Paint",categories:["hand-drawn","playful","childlike","casual","sketchy"],description:"Bold hand-drawn style, playful and energetic",artistic:!0},{name:"Hand-drawn Script",googleFontsName:"Dancing Script",categories:["hand-drawn","script","elegant","flowing","cursive"],description:"Elegant flowing script, graceful and organic",artistic:!1},{name:"Gothic Blackletter",googleFontsName:"UnifrakturMaguntia",categories:["gothic","medieval","blackletter","ornate","historical"],description:"Medieval blackletter style, ornate and historical",artistic:!0},{name:"Gothic Horror",googleFontsName:"Creepster",categories:["gothic","horror","creepy","dripping","display","striking"],description:"Horror-style font with dripping effects, very striking",artistic:!0},{name:"Gothic Bold",googleFontsName:"Eater",categories:["gothic","bold","aggressive","display","striking"],description:"Bold aggressive display font, powerful impact",artistic:!0},{name:"Futuristic Digital",googleFontsName:"Orbitron",categories:["futuristic","sci-fi","digital","tech","modern","geometric"],description:"Futuristic geometric font, tech-forward and modern",artistic:!0},{name:"Futuristic Display",googleFontsName:"Bungee",categories:["futuristic","display","bold","condensed","striking"],description:"Bold condensed display font, high impact",artistic:!0},{name:"Futuristic Outline",googleFontsName:"Bungee Shade",categories:["futuristic","outline","display","bold","striking"],description:"Outlined version of Bungee, bold and striking",artistic:!0},{name:"Retro Terminal",googleFontsName:"VT323",categories:["retro","terminal","monospace","pixel","80s","tech"],description:"Retro terminal font, pixelated and nostalgic",artistic:!0},{name:"Retro Pixel",googleFontsName:"Press Start 2P",categories:["retro","pixel","8-bit","arcade","nostalgic","display"],description:"8-bit pixel font, classic arcade style",artistic:!0},{name:"Retro Display",googleFontsName:"Frijole",categories:["retro","playful","rounded","display","casual"],description:"Playful rounded retro font, fun and casual",artistic:!0},{name:"Decorative Ornate",googleFontsName:"Fascinate",categories:["decorative","ornate","display","striking","elaborate"],description:"Highly decorative display font, ornate and elaborate",artistic:!0},{name:"Decorative Outline",googleFontsName:"Fascinate Inline",categories:["decorative","outline","display","ornate","striking"],description:"Outlined decorative font, ornate and striking",artistic:!0},{name:"Decorative Script",googleFontsName:"Fredericka the Great",categories:["decorative","script","ornate","elegant","display"],description:"Elegant decorative script, ornate and sophisticated",artistic:!0},{name:"Horror Dripping",googleFontsName:"Nosifer",categories:["horror","creepy","dripping","blood","striking","display"],description:"Creepy font with blood-dripping effects, very striking",artistic:!0},{name:"Tech Monospace",googleFontsName:"Share Tech Mono",categories:["tech","monospace","terminal","code","modern"],description:"Clean tech monospace font, modern and readable",artistic:!1},{name:"Tech Display",googleFontsName:"Rajdhani",categories:["tech","modern","geometric","sans-serif","futuristic"],description:"Modern geometric tech font, clean and futuristic",artistic:!1},{name:"Organic Flowing",googleFontsName:"Dancing Script",categories:["organic","flowing","natural","script","elegant"],description:"Flowing organic script, natural and elegant",artistic:!1},{name:"Modern Sans",googleFontsName:"Roboto",categories:["modern","clean","sans-serif","readable","professional"],description:"Clean modern sans-serif, professional and readable",artistic:!1},{name:"Modern Serif",googleFontsName:"Playfair Display",categories:["modern","serif","elegant","sophisticated","readable"],description:"Elegant modern serif, sophisticated and readable",artistic:!1},{name:"Bold Condensed",googleFontsName:"Bungee",categories:["bold","condensed","display","striking","impact"],description:"Bold condensed display font, high impact",artistic:!0},{name:"Bold Aggressive",googleFontsName:"Eater",categories:["bold","aggressive","display","striking","powerful"],description:"Bold aggressive display font, powerful and striking",artistic:!0}];function y(n){const t=n.toLowerCase();return x.map(e=>{let i=0;for(const s of e.categories)t.includes(s)&&(i+=10);return(e.name.toLowerCase().includes(t)||t.includes(e.name.toLowerCase()))&&(i+=15),(e.description.toLowerCase().includes(t)||t.includes(e.description.toLowerCase()))&&(i+=8),e.artistic&&(i+=1),{font:e,score:i}}).filter(e=>e.score>0).sort((e,i)=>i.score-e.score).map(e=>e.font)}function M(n){return y(n)[0]}const F=new Set;function S(n){return new Promise((t,o)=>{const e=n.replace(/\s+/g,"+");if(F.has(e)){t();return}if(document.querySelector(`link[href*="fonts.googleapis.com"][href*="${e}"]`)){F.add(e),t();return}const s=document.createElement("link");s.rel="stylesheet",s.href=`https://fonts.googleapis.com/css2?family=${e}:wght@400&display=swap`,s.onload=()=>{F.add(e),t()},s.onerror=()=>{console.warn(`Failed to load Google Font: ${n}`),o(new Error(`Failed to load Google Font: ${n}`))},document.head.appendChild(s)})}function N(n){return Promise.all(n.map(t=>S(t)))}function D(n){const t=n.replace(/\s+/g,"+");return F.has(t)}function A(n,t="sans-serif"){const o=["Caveat","Dancing Script","Finger Paint","Fredericka the Great"],e=["VT323","Press Start 2P","Share Tech Mono"];let i=t;return o.includes(n)?i="cursive":e.includes(n)&&(i="monospace"),`'${n}', ${i}`}function T(n,t){const o=n.toLowerCase(),e=t.rejectedFont.toLowerCase(),i=t.negativeAspects.map(a=>a.toLowerCase()),s=t.positiveAspects.map(a=>a.toLowerCase());return y(n).filter(a=>a.googleFontsName.toLowerCase()!==e&&a.name.toLowerCase()!==e).map(a=>{let c=0;for(const r of a.categories)o.includes(r)&&(c+=5);for(const r of s)(a.categories.some(m=>m.includes(r)||r.includes(m))||a.name.toLowerCase().includes(r)||a.description.toLowerCase().includes(r))&&(c+=15);for(const r of i)(a.categories.some(m=>m.includes(r)||r.includes(m))||a.name.toLowerCase().includes(r)||a.description.toLowerCase().includes(r))&&(c-=20);const u=s.some(r=>r.includes("striking")||r.includes("artistic")||r.includes("unique"));return u&&a.artistic&&(c+=10),u&&!a.artistic&&(c-=5),{font:a,score:c}}).filter(a=>a.score>0).sort((a,c)=>c.score-a.score).map(a=>a.font)}function L(n,t){return T(n,t)[0]}if(typeof window<"u"){const n={AnimatedText:b,FallingAnimation:f,SplittingAnimation:w,GlitchingAnimation:C,FloatingAnimation:v,calculateDisintegration:k,fontSuggestions:x,suggestFonts:y,suggestFont:M,loadGoogleFont:S,loadGoogleFonts:N,isFontLoaded:D,getFontFamily:A,refineSuggestion:T,refineFont:L};window.TypographyToolkit=n,window.AnimatedText=b}return d.AnimatedText=b,d.FallingAnimation=f,d.FloatingAnimation=v,d.GlitchingAnimation=C,d.SplittingAnimation=w,d.calculateDisintegration=k,d.fontSuggestions=x,d.getFontFamily=A,d.isFontLoaded=D,d.loadGoogleFont=S,d.loadGoogleFonts=N,d.refineFont=L,d.refineSuggestion=T,d.suggestFont=M,d.suggestFonts=y,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}),d}({});
@@ -1 +1 @@
1
- (function(l,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(l=typeof globalThis<"u"?globalThis:l||self,p(l.TypographyToolkit={}))})(this,function(l){"use strict";class p{update(t,o,n,i={}){const e=i.speed||1,s=i.amplitude||20,r=o*.2,c=(n*10*e+r*5)%s-s/2;return{...t,y:c}}}class y{update(t,o,n,i={}){const e=i.speed||1,s=i.amplitude||4,r=o*.2,d=Math.sin(n*.5*e+r)*s;return{...t,x:d}}}class f{constructor(){this.lastUpdate=0,this.glitchX=0,this.glitchY=0,this.glitchRot=0}update(t,o,n,i={}){const e=i.amplitude||3;return n-this.lastUpdate>.1&&(this.glitchX=(Math.random()-.5)*e,this.glitchY=(Math.random()-.5)*(e*.67),this.glitchRot=(Math.random()-.5)*e,this.lastUpdate=n),{...t,x:this.glitchX,y:this.glitchY,rotation:this.glitchRot}}}class g{update(t,o,n,i={}){const e=i.speed||1,s=i.amplitude||15,r=o*.2,c=-((n*8*e+r*4)%s-s/2);return{...t,y:c}}}function b(h,t){const{x:o,y:n,rotation:i,scale:e,opacity:s}=t;h.style.transform=`translate(${o}px, ${n}px) rotate(${i}deg) scale(${e})`,h.style.opacity=s.toString()}function x(h,t,o,n,i,e){if(!e.enabled)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const s=e.radius||80,r=e.strength||1,d=e.behaviors||["fall-away","split-apart","explode"],c=o-h,u=n-t,T=Math.sqrt(c*c+u*u);if(T>=s)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const a=(1-T/s)*r,C=Math.atan2(u,c),w=d[i%d.length];let m;switch(w){case"fall-away":const F=Math.cos(C)*a*20,S=Math.sin(C)*a*40+a*30;m={x:F,y:S,rotation:a*15,scale:1,opacity:1-a*.6};break;case"split-apart":const v=i%2===0?1:-1;m={x:v*a*50,y:(Math.random()-.5)*a*10,rotation:a*10*v,scale:1,opacity:1-a*.6};break;case"explode":const M=C+(Math.random()-.5)*.5;m={x:Math.cos(M)*a*40,y:Math.sin(M)*a*40,rotation:a*30,scale:1+a*.4,opacity:1-a*.6};break;default:m={x:0,y:0,rotation:0,scale:1,opacity:1}}return{state:m,applied:!0}}class A{constructor(t){this.letters=[],this.animationFrame=null,this.mouseX=0,this.mouseY=0,this.mouseMoveHandler=null,this.startTime=Date.now(),this.isDestroyed=!1,this.container=t.container,this.animationTypes=t.animations||["falling","splitting","glitching","floating"],this.cycle=t.cycle!==!1,this.speed=t.speed||1,this.amplitude=t.amplitude||1,this.disintegration=t.disintegration||{enabled:!1},this.style=t.style||{},this.fadeOut=t.fadeOut||0,this.textContainer=document.createElement("div"),this.textContainer.style.position="absolute",this.textContainer.style.pointerEvents="none",this.textContainer.style.zIndex="1",t.position&&(t.position.x!==void 0&&(this.textContainer.style.left=`${t.position.x}px`),t.position.y!==void 0&&(this.textContainer.style.top=`${t.position.y}px`)),this.createLetters(t.text),this.setupMouseTracking(),this.startAnimation(),this.fadeOut>0&&setTimeout(()=>this.destroy(),this.fadeOut)}createLetters(t){t.toUpperCase().split("").forEach((n,i)=>{if(n===" "){const r=document.createTextNode(" ");this.textContainer.appendChild(r);return}const e=document.createElement("span");e.className="animated-letter",e.textContent=n,e.dataset.index=i.toString(),e.dataset.char=n,this.applyStyle(e),e.style.display="inline-block",e.style.position="relative",e.style.transition="transform 0.1s ease-out",this.textContainer.appendChild(e);const s=e.getBoundingClientRect();this.letters.push({element:e,index:i,char:n,baseX:s.left,baseY:s.top})}),this.container.appendChild(this.textContainer)}applyStyle(t){this.style.fontFamily&&(t.style.fontFamily=this.style.fontFamily),this.style.fontSize&&(t.style.fontSize=`${this.style.fontSize}px`),this.style.color&&(t.style.color=this.style.color),this.style.fontWeight&&(t.style.fontWeight=this.style.fontWeight)}setupMouseTracking(){this.disintegration.enabled&&(this.mouseMoveHandler=t=>{this.mouseX=t.clientX,this.mouseY=t.clientY},document.addEventListener("mousemove",this.mouseMoveHandler))}startAnimation(){const t=()=>{if(this.isDestroyed)return;const o=(Date.now()-this.startTime)*.001;this.letters.forEach((n,i)=>{const e=n.element.getBoundingClientRect(),s=e.left+e.width/2,r=e.top+e.height/2,d=x(s,r,this.mouseX,this.mouseY,i,this.disintegration);let c;if(d.applied)c=d.state;else{const u=this.getAnimationType(i);c=this.getAnimation(u).update({x:0,y:0,rotation:0,scale:1,opacity:1},i,o,{speed:this.speed,amplitude:this.amplitude*(u==="falling"||u==="floating"?20:4)})}b(n.element,c)}),this.animationFrame=requestAnimationFrame(t)};this.animationFrame=requestAnimationFrame(t)}getAnimationType(t){return this.cycle?this.animationTypes[t%this.animationTypes.length]:this.animationTypes[0]}getAnimation(t){switch(t){case"falling":return new p;case"splitting":return new y;case"glitching":return new f;case"floating":return new g;default:return new p}}destroy(){this.isDestroyed||(this.isDestroyed=!0,this.animationFrame!==null&&cancelAnimationFrame(this.animationFrame),this.mouseMoveHandler&&document.removeEventListener("mousemove",this.mouseMoveHandler),this.textContainer.style.transition="opacity 3s ease",this.textContainer.style.opacity="0",setTimeout(()=>{this.textContainer.parentNode&&this.textContainer.parentNode.removeChild(this.textContainer)},3e3))}getElement(){return this.textContainer}}typeof window<"u"&&(window.TypographyToolkit={AnimatedText:A,FallingAnimation:p,SplittingAnimation:y,GlitchingAnimation:f,FloatingAnimation:g,calculateDisintegration:x}),l.AnimatedText=A,l.FallingAnimation=p,l.FloatingAnimation=g,l.GlitchingAnimation=f,l.SplittingAnimation=y,l.calculateDisintegration=x,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
1
+ (function(l,m){typeof exports=="object"&&typeof module<"u"?m(exports):typeof define=="function"&&define.amd?define(["exports"],m):(l=typeof globalThis<"u"?globalThis:l||self,m(l.TypographyToolkit={}))})(this,function(l){"use strict";class m{update(t,o,e,i={}){const s=i.speed||1,c=i.amplitude||20,h=o*.2,d=(e*10*s+h*5)%c-c/2;return{...t,y:d}}}class w{update(t,o,e,i={}){const s=i.speed||1,c=i.amplitude||4,h=o*.2,n=Math.sin(e*.5*s+h)*c;return{...t,x:n}}}class C{constructor(){this.lastUpdate=0,this.glitchX=0,this.glitchY=0,this.glitchRot=0}update(t,o,e,i={}){const s=i.amplitude||3;return e-this.lastUpdate>.1&&(this.glitchX=(Math.random()-.5)*s,this.glitchY=(Math.random()-.5)*(s*.67),this.glitchRot=(Math.random()-.5)*s,this.lastUpdate=e),{...t,x:this.glitchX,y:this.glitchY,rotation:this.glitchRot}}}class v{update(t,o,e,i={}){const s=i.speed||1,c=i.amplitude||15,h=o*.2,d=-((e*8*s+h*4)%c-c/2);return{...t,y:d}}}function B(a,t){const{x:o,y:e,rotation:i,scale:s,opacity:c}=t;a.style.transform=`translate(${o}px, ${e}px) rotate(${i}deg) scale(${s})`,a.style.opacity=c.toString()}function x(a,t,o,e,i,s){if(!s.enabled)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const c=s.radius||80,h=s.strength||1,n=s.behaviors||["fall-away","split-apart","explode"],d=o-a,u=e-t,r=Math.sqrt(d*d+u*u);if(r>=c)return{state:{x:0,y:0,rotation:0,scale:1,opacity:1},applied:!1};const g=(1-r/c)*h,p=Math.atan2(u,d),R=n[i%n.length];let f;switch(R){case"fall-away":const H=Math.cos(p)*g*20,G=Math.sin(p)*g*40+g*30;f={x:H,y:G,rotation:g*15,scale:1,opacity:1-g*.6};break;case"split-apart":const E=i%2===0?1:-1;f={x:E*g*50,y:(Math.random()-.5)*g*10,rotation:g*10*E,scale:1,opacity:1-g*.6};break;case"explode":const P=p+(Math.random()-.5)*.5;f={x:Math.cos(P)*g*40,y:Math.sin(P)*g*40,rotation:g*30,scale:1+g*.4,opacity:1-g*.6};break;default:f={x:0,y:0,rotation:0,scale:1,opacity:1}}return{state:f,applied:!0}}class b{constructor(t){var o;if(this.letters=[],this.animationFrame=null,this.mouseX=0,this.mouseY=0,this.mouseMoveHandler=null,this.startTime=Date.now(),this.isDestroyed=!1,this.container=t.container,this.animationTypes=t.animations||["falling","splitting","glitching","floating"],this.cycle=t.cycle!==!1,this.speed=t.speed||1,this.amplitude=t.amplitude||1,this.disintegration=t.disintegration||{enabled:!1},this.style=t.style||{},this.fadeOut=t.fadeOut||0,this.callbacks=t.callbacks,this.textContainer=document.createElement("div"),this.textContainer.style.position="absolute",this.textContainer.style.pointerEvents="none",this.textContainer.style.zIndex="1",t.containerClass&&(this.textContainer.className=t.containerClass),t.containerStyle&&Object.entries(t.containerStyle).forEach(([e,i])=>{this.textContainer.style.setProperty(e,i)}),t.position){let e=t.position.x,i=t.position.y;if(t.position.constrainToViewport){this.container.getBoundingClientRect();const s=window.innerWidth,c=window.innerHeight,h=t.text.length*20;e!==void 0&&(e=Math.max(0,Math.min(e,s-h))),i!==void 0&&(i=Math.max(0,Math.min(i,c-50)))}e!==void 0&&(this.textContainer.style.left=`${e}px`),i!==void 0&&(this.textContainer.style.top=`${i}px`)}this.createLetters(t.text),this.setupMouseTracking(),this.startAnimation(),(o=this.callbacks)!=null&&o.onCreate&&this.callbacks.onCreate(this.textContainer),this.fadeOut>0&&setTimeout(()=>this.destroy(),this.fadeOut)}createLetters(t){t.toUpperCase().split("").forEach((e,i)=>{if(e===" "){const h=document.createTextNode(" ");this.textContainer.appendChild(h);return}const s=document.createElement("span");s.className="animated-letter",s.textContent=e,s.dataset.index=i.toString(),s.dataset.char=e,this.applyStyle(s),s.style.display="inline-block",s.style.position="relative",s.style.transition="transform 0.1s ease-out",this.textContainer.appendChild(s);const c=s.getBoundingClientRect();this.letters.push({element:s,index:i,char:e,baseX:c.left,baseY:c.top})}),this.container.appendChild(this.textContainer)}applyStyle(t){this.style.fontFamily&&(t.style.fontFamily=this.style.fontFamily),this.style.fontSize&&(t.style.fontSize=`${this.style.fontSize}px`),this.style.color&&(t.style.color=this.style.color),this.style.fontWeight&&(t.style.fontWeight=this.style.fontWeight),this.style.textShadow&&(t.style.textShadow=this.style.textShadow),this.style.letterSpacing&&(t.style.letterSpacing=this.style.letterSpacing),this.style.textTransform&&(t.style.textTransform=this.style.textTransform)}setupMouseTracking(){this.disintegration.enabled&&(this.mouseMoveHandler=t=>{this.mouseX=t.clientX,this.mouseY=t.clientY},document.addEventListener("mousemove",this.mouseMoveHandler))}startAnimation(){const t=()=>{if(this.isDestroyed)return;const o=(Date.now()-this.startTime)*.001;this.letters.forEach((e,i)=>{var u;const s=e.element.getBoundingClientRect(),c=s.left+s.width/2,h=s.top+s.height/2,n=x(c,h,this.mouseX,this.mouseY,i,this.disintegration);let d;if(n.applied)d=n.state,(u=this.callbacks)!=null&&u.onDisintegrate&&this.callbacks.onDisintegrate(i);else{const r=this.getAnimationType(i);d=this.getAnimation(r).update({x:0,y:0,rotation:0,scale:1,opacity:1},i,o,{speed:this.speed,amplitude:this.amplitude*(r==="falling"||r==="floating"?20:4)})}B(e.element,d)}),this.animationFrame=requestAnimationFrame(t)};this.animationFrame=requestAnimationFrame(t)}getAnimationType(t){return this.cycle?this.animationTypes[t%this.animationTypes.length]:this.animationTypes[0]}getAnimation(t){switch(t){case"falling":return new m;case"splitting":return new w;case"glitching":return new C;case"floating":return new v;default:return new m}}destroy(){var t;this.isDestroyed||(this.isDestroyed=!0,this.animationFrame!==null&&cancelAnimationFrame(this.animationFrame),this.mouseMoveHandler&&document.removeEventListener("mousemove",this.mouseMoveHandler),(t=this.callbacks)!=null&&t.onDestroy&&this.callbacks.onDestroy(),this.textContainer.style.transition="opacity 3s ease",this.textContainer.style.opacity="0",setTimeout(()=>{this.textContainer.parentNode&&this.textContainer.parentNode.removeChild(this.textContainer)},3e3))}getElement(){return this.textContainer}}const k=[{name:"Hand-drawn Casual",googleFontsName:"Caveat",categories:["hand-drawn","handwriting","casual","sketchy","informal"],description:"Casual handwritten style, friendly and approachable",artistic:!1},{name:"Hand-drawn Playful",googleFontsName:"Finger Paint",categories:["hand-drawn","playful","childlike","casual","sketchy"],description:"Bold hand-drawn style, playful and energetic",artistic:!0},{name:"Hand-drawn Script",googleFontsName:"Dancing Script",categories:["hand-drawn","script","elegant","flowing","cursive"],description:"Elegant flowing script, graceful and organic",artistic:!1},{name:"Gothic Blackletter",googleFontsName:"UnifrakturMaguntia",categories:["gothic","medieval","blackletter","ornate","historical"],description:"Medieval blackletter style, ornate and historical",artistic:!0},{name:"Gothic Horror",googleFontsName:"Creepster",categories:["gothic","horror","creepy","dripping","display","striking"],description:"Horror-style font with dripping effects, very striking",artistic:!0},{name:"Gothic Bold",googleFontsName:"Eater",categories:["gothic","bold","aggressive","display","striking"],description:"Bold aggressive display font, powerful impact",artistic:!0},{name:"Futuristic Digital",googleFontsName:"Orbitron",categories:["futuristic","sci-fi","digital","tech","modern","geometric"],description:"Futuristic geometric font, tech-forward and modern",artistic:!0},{name:"Futuristic Display",googleFontsName:"Bungee",categories:["futuristic","display","bold","condensed","striking"],description:"Bold condensed display font, high impact",artistic:!0},{name:"Futuristic Outline",googleFontsName:"Bungee Shade",categories:["futuristic","outline","display","bold","striking"],description:"Outlined version of Bungee, bold and striking",artistic:!0},{name:"Retro Terminal",googleFontsName:"VT323",categories:["retro","terminal","monospace","pixel","80s","tech"],description:"Retro terminal font, pixelated and nostalgic",artistic:!0},{name:"Retro Pixel",googleFontsName:"Press Start 2P",categories:["retro","pixel","8-bit","arcade","nostalgic","display"],description:"8-bit pixel font, classic arcade style",artistic:!0},{name:"Retro Display",googleFontsName:"Frijole",categories:["retro","playful","rounded","display","casual"],description:"Playful rounded retro font, fun and casual",artistic:!0},{name:"Decorative Ornate",googleFontsName:"Fascinate",categories:["decorative","ornate","display","striking","elaborate"],description:"Highly decorative display font, ornate and elaborate",artistic:!0},{name:"Decorative Outline",googleFontsName:"Fascinate Inline",categories:["decorative","outline","display","ornate","striking"],description:"Outlined decorative font, ornate and striking",artistic:!0},{name:"Decorative Script",googleFontsName:"Fredericka the Great",categories:["decorative","script","ornate","elegant","display"],description:"Elegant decorative script, ornate and sophisticated",artistic:!0},{name:"Horror Dripping",googleFontsName:"Nosifer",categories:["horror","creepy","dripping","blood","striking","display"],description:"Creepy font with blood-dripping effects, very striking",artistic:!0},{name:"Tech Monospace",googleFontsName:"Share Tech Mono",categories:["tech","monospace","terminal","code","modern"],description:"Clean tech monospace font, modern and readable",artistic:!1},{name:"Tech Display",googleFontsName:"Rajdhani",categories:["tech","modern","geometric","sans-serif","futuristic"],description:"Modern geometric tech font, clean and futuristic",artistic:!1},{name:"Organic Flowing",googleFontsName:"Dancing Script",categories:["organic","flowing","natural","script","elegant"],description:"Flowing organic script, natural and elegant",artistic:!1},{name:"Modern Sans",googleFontsName:"Roboto",categories:["modern","clean","sans-serif","readable","professional"],description:"Clean modern sans-serif, professional and readable",artistic:!1},{name:"Modern Serif",googleFontsName:"Playfair Display",categories:["modern","serif","elegant","sophisticated","readable"],description:"Elegant modern serif, sophisticated and readable",artistic:!1},{name:"Bold Condensed",googleFontsName:"Bungee",categories:["bold","condensed","display","striking","impact"],description:"Bold condensed display font, high impact",artistic:!0},{name:"Bold Aggressive",googleFontsName:"Eater",categories:["bold","aggressive","display","striking","powerful"],description:"Bold aggressive display font, powerful and striking",artistic:!0}];function y(a){const t=a.toLowerCase();return k.map(e=>{let i=0;for(const s of e.categories)t.includes(s)&&(i+=10);return(e.name.toLowerCase().includes(t)||t.includes(e.name.toLowerCase()))&&(i+=15),(e.description.toLowerCase().includes(t)||t.includes(e.description.toLowerCase()))&&(i+=8),e.artistic&&(i+=1),{font:e,score:i}}).filter(e=>e.score>0).sort((e,i)=>i.score-e.score).map(e=>e.font)}function M(a){return y(a)[0]}const F=new Set;function S(a){return new Promise((t,o)=>{const e=a.replace(/\s+/g,"+");if(F.has(e)){t();return}if(document.querySelector(`link[href*="fonts.googleapis.com"][href*="${e}"]`)){F.add(e),t();return}const s=document.createElement("link");s.rel="stylesheet",s.href=`https://fonts.googleapis.com/css2?family=${e}:wght@400&display=swap`,s.onload=()=>{F.add(e),t()},s.onerror=()=>{console.warn(`Failed to load Google Font: ${a}`),o(new Error(`Failed to load Google Font: ${a}`))},document.head.appendChild(s)})}function N(a){return Promise.all(a.map(t=>S(t)))}function D(a){const t=a.replace(/\s+/g,"+");return F.has(t)}function A(a,t="sans-serif"){const o=["Caveat","Dancing Script","Finger Paint","Fredericka the Great"],e=["VT323","Press Start 2P","Share Tech Mono"];let i=t;return o.includes(a)?i="cursive":e.includes(a)&&(i="monospace"),`'${a}', ${i}`}function T(a,t){const o=a.toLowerCase(),e=t.rejectedFont.toLowerCase(),i=t.negativeAspects.map(n=>n.toLowerCase()),s=t.positiveAspects.map(n=>n.toLowerCase());return y(a).filter(n=>n.googleFontsName.toLowerCase()!==e&&n.name.toLowerCase()!==e).map(n=>{let d=0;for(const r of n.categories)o.includes(r)&&(d+=5);for(const r of s)(n.categories.some(p=>p.includes(r)||r.includes(p))||n.name.toLowerCase().includes(r)||n.description.toLowerCase().includes(r))&&(d+=15);for(const r of i)(n.categories.some(p=>p.includes(r)||r.includes(p))||n.name.toLowerCase().includes(r)||n.description.toLowerCase().includes(r))&&(d-=20);const u=s.some(r=>r.includes("striking")||r.includes("artistic")||r.includes("unique"));return u&&n.artistic&&(d+=10),u&&!n.artistic&&(d-=5),{font:n,score:d}}).filter(n=>n.score>0).sort((n,d)=>d.score-n.score).map(n=>n.font)}function L(a,t){return T(a,t)[0]}if(typeof window<"u"){const a={AnimatedText:b,FallingAnimation:m,SplittingAnimation:w,GlitchingAnimation:C,FloatingAnimation:v,calculateDisintegration:x,fontSuggestions:k,suggestFonts:y,suggestFont:M,loadGoogleFont:S,loadGoogleFonts:N,isFontLoaded:D,getFontFamily:A,refineSuggestion:T,refineFont:L};window.TypographyToolkit=a,window.AnimatedText=b}l.AnimatedText=b,l.FallingAnimation=m,l.FloatingAnimation=v,l.GlitchingAnimation=C,l.SplittingAnimation=w,l.calculateDisintegration=x,l.fontSuggestions=k,l.getFontFamily=A,l.isFontLoaded=D,l.loadGoogleFont=S,l.loadGoogleFonts=N,l.refineFont=L,l.refineSuggestion=T,l.suggestFont=M,l.suggestFonts=y,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typography-toolkit",
3
- "version": "1.0.0",
4
- "description": "Letter-by-letter text animations with proximity-based disintegration effects",
3
+ "version": "1.2.0",
4
+ "description": "Letter-by-letter text animations with proximity-based disintegration effects and Google Fonts selection",
5
5
  "main": "dist/typography-toolkit.umd.js",
6
6
  "module": "dist/typography-toolkit.esm.js",
7
7
  "types": "dist/index.d.ts",
@@ -23,7 +23,10 @@
23
23
  "letters",
24
24
  "disintegration",
25
25
  "interactive",
26
- "dom"
26
+ "dom",
27
+ "fonts",
28
+ "google-fonts",
29
+ "font-selection"
27
30
  ],
28
31
  "author": "",
29
32
  "license": "MIT",