@edwinvakayil/calligraphy 1.4.0 → 1.5.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
@@ -1,6 +1,6 @@
1
1
  # @edwinvakayil/calligraphy
2
2
 
3
- A lightweight **React + TypeScript** typography component with automatic **Google Fonts** support, **hero entrance animations**, and an **italic accent toggle**.
3
+ A lightweight **React + TypeScript** typography component with automatic **Google Fonts** support, **30+ hero entrance animations**, **custom motion config**, and an **italic accent toggle**.
4
4
 
5
5
  ---
6
6
 
@@ -44,20 +44,20 @@ The `font` prop auto-injects the matching Google Font `<link>` tag — no manual
44
44
 
45
45
  ## Variants
46
46
 
47
- | Variant | Tag | Role |
48
- |--------------|----------|-----------------------------------|
49
- | `Display` | `h1` | Hero / landing page headline |
50
- | `H1` | `h1` | Primary page heading |
51
- | `H2` | `h2` | Section heading |
52
- | `H3` | `h3` | Sub-section heading |
53
- | `H4` | `h4` | Card / panel heading |
54
- | `H5` | `h5` | Small heading |
55
- | `H6` | `h6` | Micro heading |
56
- | `Subheading` | `h6` | Supporting subtitle |
57
- | `Overline` | `span` | ALL CAPS label above a heading |
58
- | `Body` | `p` | Main body copy |
59
- | `Label` | `label` | Form labels, tags |
60
- | `Caption` | `span` | Image captions, fine print |
47
+ | Variant | Tag | Role |
48
+ |--------------|----------|--------------------------------|
49
+ | `Display` | `h1` | Hero / landing page headline |
50
+ | `H1` | `h1` | Primary page heading |
51
+ | `H2` | `h2` | Section heading |
52
+ | `H3` | `h3` | Sub-section heading |
53
+ | `H4` | `h4` | Card / panel heading |
54
+ | `H5` | `h5` | Small heading |
55
+ | `H6` | `h6` | Micro heading |
56
+ | `Subheading` | `h6` | Supporting subtitle |
57
+ | `Overline` | `span` | ALL CAPS label above a heading |
58
+ | `Body` | `p` | Main body copy |
59
+ | `Label` | `label` | Form labels, tags |
60
+ | `Caption` | `span` | Image captions, fine print |
61
61
 
62
62
  ---
63
63
 
@@ -65,18 +65,20 @@ The `font` prop auto-injects the matching Google Font `<link>` tag — no manual
65
65
 
66
66
  ```ts
67
67
  interface TypographyProps {
68
- variant?: TypographyVariant // default: "Body"
69
- font?: string // Google Font name e.g. "Bricolage Grotesque"
70
- color?: string // Any CSS color value
71
- align?: "left" | "center" | "right" | "justify"
72
- as?: ElementType // Override rendered HTML tag
73
- truncate?: boolean // Single-line ellipsis
74
- maxLines?: number // Multi-line line-clamp
75
- animation?: HeroAnimation // Entrance animation (Display / H1 only)
76
- italic?: boolean // default: trueshow serif italic accent on <em>
77
- accentColor?: string // default: "#c8b89a" color for <em> italic text
78
- className?: string
79
- style?: CSSProperties
68
+ variant?: TypographyVariant // default: "Body"
69
+ font?: string // Google Font name e.g. "Bricolage Grotesque"
70
+ color?: string // Any CSS color value
71
+ align?: "left" | "center" | "right" | "justify"
72
+ as?: ElementType // Override rendered HTML tag
73
+ truncate?: boolean // Single-line ellipsis
74
+ maxLines?: number // Multi-line line-clamp
75
+ animation?: HeroAnimation // Built-in entrance animation (Display / H1 only)
76
+ motionConfig?: MotionConfig // Custom keyframe animation any variant
77
+ motionRef?: (el: HTMLElement | null) => void // Direct DOM access for GSAP / Framer
78
+ italic?: boolean // default: false — serif italic accent on <em>
79
+ accentColor?: string // default: "#c8b89a" — color for <em> italic text
80
+ className?: string
81
+ style?: CSSProperties
80
82
  }
81
83
  ```
82
84
 
@@ -84,7 +86,7 @@ interface TypographyProps {
84
86
 
85
87
  ## Hero Animations
86
88
 
87
- The `animation` prop works on `Display` and `H1` variants only. It injects a tiny stylesheet once (no external dependency) and applies a CSS keyframe entrance.
89
+ The `animation` prop works on `Display` and `H1` variants. It injects a tiny stylesheet once (no external dependency) and applies a CSS keyframe entrance.
88
90
 
89
91
  ```tsx
90
92
  <Typography variant="Display" font="Bricolage Grotesque" animation="rise">
@@ -94,141 +96,220 @@ The `animation` prop works on `Display` and `H1` variants only. It injects a tin
94
96
 
95
97
  ### Available animations
96
98
 
97
- | Value | Description |
98
- |---------------|-------------------------------------------------------|
99
- | `rise` | Smooth upward fade-in — clean, universal |
100
- | `stagger` | Each word rises in sequence |
101
- | `clip` | Text unmasked left to right editorial feel |
102
- | `pop` | Spring scale-in energetic and confident |
103
- | `letters` | Each letter slides in with a slight rotation |
104
- | `blur` | Emerges from a blur cinematic and dreamy |
105
- | `flip` | 3-D perspective rotate on entry depth and gravitas |
106
- | `swipe` | Slides in from the rightdirectional flow |
107
- | `typewriter` | Character-by-character revealfocused |
108
- | `bounce` | Drops from above with a soft bounce |
99
+ #### Original
100
+
101
+ | Value | Description |
102
+ |--------------|------------------------------------------------------|
103
+ | `rise` | Smooth upward fade-inclean, universal |
104
+ | `stagger` | Each word rises in sequence |
105
+ | `clip` | Text unmasked left to right editorial feel |
106
+ | `pop` | Spring scale-inenergetic and confident |
107
+ | `letters` | Each letter slides in with a slight rotation |
108
+ | `blur` | Emerges from a blurcinematic and dreamy |
109
+ | `flip` | 3-D perspective rotate on entry depth and gravitas |
110
+ | `swipe` | Slides in from the right directional flow |
111
+ | `typewriter` | Character-by-character reveal — focused |
112
+ | `bounce` | Drops from above with a soft bounce |
113
+
114
+ #### Modern
115
+
116
+ | Value | Description |
117
+ |-------------|----------------------------------------------------------|
118
+ | `velvet` | Words drift in with a soft skew — buttery & modern |
119
+ | `curtain` | Each word clips upward like a rising curtain |
120
+ | `morph` | Squash-and-stretch spring — expressive & bold |
121
+ | `ground` | Words emerge from behind the baseline — editorial |
122
+ | `cascade` | Diagonal character waterfall — dynamic & layered |
123
+ | `spotlight` | Expands from compressed letterspace — cinematic |
124
+ | `ink` | Words fade in with a gentle scale — calm & precise |
125
+ | `hinge` | Words rotate in from their left edge — mechanical |
126
+ | `stretch` | Horizontal rubber-band expand — playful & punchy |
127
+ | `peel` | Bottom-to-top clip reveal per word — sharp |
128
+ | `ripple` | Words scale out from a compressed point — elastic |
129
+ | `cinch` | Characters pinch then snap open — crisp & mechanical |
130
+ | `tiltrise` | Words rise while untilting from a lean — editorial |
131
+
132
+ #### New mechanics
133
+
134
+ | Value | Description |
135
+ |--------------|----------------------------------------------------------|
136
+ | `unfurl` | Line expands from horizontal center — origami opening |
137
+ | `billboard` | Whole line rotates in on Y-axis — turning sign |
138
+ | `tectonic` | Words slam from alternating sides with skew |
139
+ | `stratify` | Per-word Z-depth flight through blur — depth-of-field |
140
+ | `orbit` | Words grow from dot while rotating on Z-axis |
141
+ | `liquid` | Per-word cross-axis squash then spring — fluid inertia |
142
+ | `noiseFade` | 3 random opacity waveforms per word — signal locking |
143
+ | `slab` | Words stamp from left with scaleX — print-press energy |
144
+ | `thread` | Characters float on individual sine-wave Y offsets |
145
+ | `glassReveal`| Backdrop blur evaporates as text solidifies |
146
+ | `wordPop` | Per-word spring from zero scale at its own centre |
147
+ | `scanline` | Single-pixel horizontal slice expands to full height |
148
+ | `chromaShift`| RGB channel offsets collapse to zero — printing register |
149
+ | `wordFade` | Per-word cross-dissolve with warmth scale |
150
+ | `rotateIn` | Full Y-axis card-flip per word — face-up reveal |
151
+ | `pressIn` | Presses to 0.92 then springs past 1 — physical button |
152
+ | `unfurl` | Text splits open from center — origami feel |
153
+ | `dissolve` | Characters dissolve in with random micro-rotation |
154
+ | `depth` | Z-axis push from far to near — immersive 3D |
155
+ | `maskSweep` | Mask-image sweep reveals text left to right |
156
+ | `gradSweep` | Gradient sweep across text then resolves to solid |
109
157
 
110
158
  All animations use only `transform`, `opacity`, and `filter` — GPU-composited, no layout thrashing, 60fps safe.
111
159
 
112
160
  ---
113
161
 
114
- ## Italic Accent
162
+ ## Custom Motion — `motionConfig`
115
163
 
116
- When `italic={true}` (the default), any `<em>` tag inside a `Display` or `H1` hero renders in **Instrument Serif italic** with an accent color. This creates a classic editorial contrast between a bold sans-serif header and a refined serif word.
164
+ When the built-in presets don't fit, write your own keyframe. Works on **any variant**, not just heroes.
117
165
 
118
166
  ```tsx
119
- // With italic accent (default)
120
- <Typography variant="Display" font="Bricolage Grotesque">
121
- Build with <em>intention</em>
122
- </Typography>
167
+ import { Typography, type MotionConfig } from "@edwinvakayil/calligraphy";
168
+ ```
123
169
 
124
- // Turn off — everything renders in the heading font
125
- <Typography variant="Display" font="Bricolage Grotesque" italic={false}>
126
- Build with <em>intention</em>
170
+ ### Whole-element animation
171
+
172
+ ```tsx
173
+ <Typography
174
+ variant="H2"
175
+ font="Syne"
176
+ motionConfig={{
177
+ keyframes: `from { opacity: 0; transform: translateY(24px) skewX(6deg); }
178
+ to { opacity: 1; transform: none; }`,
179
+ duration: "0.8s",
180
+ easing: "cubic-bezier(0.16, 1, 0.3, 1)",
181
+ delay: "0.1s",
182
+ }}
183
+ >
184
+ Section heading
127
185
  </Typography>
128
186
  ```
129
187
 
130
- ### Custom accent color
188
+ ### Per-word stagger
131
189
 
132
190
  ```tsx
133
191
  <Typography
134
192
  variant="Display"
135
193
  font="Bricolage Grotesque"
136
- accentColor="#7F77DD"
194
+ motionConfig={{
195
+ keyframes: `from { opacity: 0; transform: translateX(-20px) rotate(-4deg); }
196
+ to { opacity: 1; transform: none; }`,
197
+ duration: "0.65s",
198
+ split: "words",
199
+ staggerDelay: 0.09,
200
+ }}
137
201
  >
138
- Crafted with <em>precision</em>
202
+ Design with <em>intention</em>
139
203
  </Typography>
140
204
  ```
141
205
 
142
- The `accentColor` prop only affects the `<em>` italic span. It has no effect when `italic={false}`.
143
-
144
- ---
145
-
146
- ## Examples
147
-
148
- ### Overline + Display combo
206
+ ### Per-character stagger
149
207
 
150
208
  ```tsx
151
- <Typography variant="Overline" color="#6366f1">
152
- New Feature
153
- </Typography>
154
209
  <Typography
155
210
  variant="Display"
156
- font="Bricolage Grotesque"
157
- animation="clip"
158
- accentColor="#6366f1"
211
+ motionConfig={{
212
+ keyframes: `from { opacity: 0; transform: scaleY(0) translateY(10px); }
213
+ to { opacity: 1; transform: none; }`,
214
+ duration: "0.5s",
215
+ split: "chars",
216
+ staggerDelay: 0.035,
217
+ }}
159
218
  >
160
- Build faster with <em>types</em>
219
+ Motion
161
220
  </Typography>
162
221
  ```
163
222
 
164
- ### Body + Caption
223
+ ### `MotionConfig` shape
165
224
 
166
- ```tsx
167
- <Typography variant="Body" font="Lora">
168
- A well-set paragraph in a refined serif font brings reading pleasure.
169
- </Typography>
170
- <Typography variant="Caption" color="#888">
171
- Fig. 1 — System architecture overview
172
- </Typography>
225
+ ```ts
226
+ interface MotionConfig {
227
+ keyframes: string // CSS keyframe body (without @keyframes name {})
228
+ duration?: string // default: "0.8s"
229
+ easing?: string // default: "cubic-bezier(0.16, 1, 0.3, 1)"
230
+ delay?: string // default: "0s"
231
+ fillMode?: "none"|"forwards"|"backwards"|"both" // default: "both"
232
+ split?: "none" | "words" | "chars" // default: "none"
233
+ staggerDelay?: number // seconds between spans; default 0.07 (words) / 0.04 (chars)
234
+ }
173
235
  ```
174
236
 
175
- ### Truncation
237
+ ---
238
+
239
+ ## Direct DOM Access — `motionRef`
240
+
241
+ For full control with GSAP, Framer Motion, or the Web Animations API. The ref callback fires after mount and on every re-render.
242
+
243
+ `motionRef` takes priority over both `animation` and `motionConfig`.
176
244
 
177
245
  ```tsx
178
- {/* Single line */}
179
- <Typography variant="H2" truncate>
180
- This very long title will be cut off with an ellipsis
246
+ // Web Animations API
247
+ <Typography
248
+ variant="Display"
249
+ font="Bricolage Grotesque"
250
+ motionRef={(el) => {
251
+ if (!el) return;
252
+ el.animate(
253
+ [{ opacity: 0, transform: "translateY(32px)" }, { opacity: 1, transform: "none" }],
254
+ { duration: 900, easing: "cubic-bezier(0.16,1,0.3,1)", fill: "both" }
255
+ );
256
+ }}
257
+ >
258
+ Full control
181
259
  </Typography>
182
260
 
183
- {/* Multi-line clamp */}
184
- <Typography variant="Body" maxLines={3}>
185
- This paragraph will show at most three lines...
261
+ // GSAP
262
+ <Typography
263
+ variant="H1"
264
+ motionRef={(el) => {
265
+ if (!el) return;
266
+ gsap.from(el, { opacity: 0, y: 40, duration: 0.9, ease: "power3.out" });
267
+ }}
268
+ >
269
+ GSAP powered
186
270
  </Typography>
187
271
  ```
188
272
 
189
- ### Override HTML tag
273
+ ### Priority order
190
274
 
191
- ```tsx
192
- <Typography variant="H2" as="div">
193
- Renders as a div, styled as H2
194
- </Typography>
275
+ ```
276
+ motionRef > motionConfig > animation > no animation
195
277
  ```
196
278
 
197
279
  ---
198
280
 
199
- ## Pre-loading fonts
281
+ ## Italic Accent
200
282
 
201
- To avoid FOUT (flash of unstyled text), pre-load fonts at the top of your app:
283
+ When `italic={true}`, any `<em>` tag inside a `Display` or `H1` hero renders in **Instrument Serif italic** with an accent color. Off by default.
202
284
 
203
285
  ```tsx
204
- import { preloadFonts } from "@edwinvakayil/calligraphy";
205
-
206
- // In your _app.tsx / main.tsx / layout.tsx
207
- preloadFonts(["Bricolage Grotesque", "Instrument Serif", "DM Sans"]);
208
- ```
286
+ // Off by default
287
+ <Typography variant="Display" font="Bricolage Grotesque">
288
+ Build with <em>intention</em>
289
+ </Typography>
209
290
 
210
- ---
291
+ // Turn on
292
+ <Typography variant="Display" font="Bricolage Grotesque" italic>
293
+ Build with <em>intention</em>
294
+ </Typography>
211
295
 
212
- ## Recommended hero font pairings
296
+ // Custom accent color
297
+ <Typography variant="Display" font="Bricolage Grotesque" italic accentColor="#7F77DD">
298
+ Crafted with <em>precision</em>
299
+ </Typography>
300
+ ```
213
301
 
214
- | Heading font | Style | Works well for |
215
- |------------------------|------------|------------------------------|
216
- | `Bricolage Grotesque` | Bold sans | Startups, SaaS, modern brand |
217
- | `Syne` | Geometric | Creative, portfolio, agency |
218
- | `Fraunces` | Serif | Editorial, luxury, fashion |
219
- | `Bebas Neue` | Condensed | Sports, bold campaigns |
220
- | `Playfair Display` | Serif | Journalism, books, culture |
221
- | `Outfit` | Clean sans | Apps, dashboards, fintech |
302
+ `accentColor` has no effect when `italic={false}`.
222
303
 
223
- All are on Google Fonts and auto-injected when passed to the `font` prop.
304
+ ---
224
305
 
225
306
  ## TypographyProvider
226
-
307
+
227
308
  Wrap your app (or a section of it) with `TypographyProvider` to set defaults once. Any prop passed directly to `<Typography>` still wins — the provider is just the fallback.
228
-
309
+
229
310
  ```tsx
230
- import { TypographyProvider, Typography } from "react-type-scale";
231
-
311
+ import { TypographyProvider, Typography } from "@edwinvakayil/calligraphy";
312
+
232
313
  export default function App() {
233
314
  return (
234
315
  <TypographyProvider
@@ -240,21 +321,16 @@ export default function App() {
240
321
  color: "#1a1a1a",
241
322
  }}
242
323
  >
243
- {/* Inherits font, accentColor, italic, animation from theme */}
324
+ {/* Inherits all theme values */}
244
325
  <Typography variant="Display">
245
326
  Build with <em>intention</em>
246
327
  </Typography>
247
-
248
- {/* Overrides just the animation — everything else from theme */}
328
+
329
+ {/* Overrides just the animation */}
249
330
  <Typography variant="H1" animation="clip">
250
331
  Another hero heading
251
332
  </Typography>
252
-
253
- {/* Overrides font only */}
254
- <Typography variant="Body" font="Lora">
255
- Body copy in a different font.
256
- </Typography>
257
-
333
+
258
334
  {/* italic=false wins over theme's italic=true */}
259
335
  <Typography variant="Display" italic={false}>
260
336
  No serif accent here
@@ -263,33 +339,33 @@ export default function App() {
263
339
  );
264
340
  }
265
341
  ```
266
-
342
+
267
343
  ### Theme shape
268
-
344
+
269
345
  ```ts
270
346
  interface TypographyTheme {
271
- font?: string // Google Font name applied to all variants
347
+ font?: string // Google Font applied to all variants
272
348
  accentColor?: string // <em> accent color for Display / H1
273
349
  italic?: boolean // italic accent on/off for Display / H1
274
350
  animation?: HeroAnimation // entrance animation for Display / H1
275
351
  color?: string // default text color for all variants
276
352
  }
277
353
  ```
278
-
354
+
279
355
  ### Priority order
280
-
356
+
281
357
  ```
282
358
  Explicit prop > TypographyProvider theme > built-in default
283
359
  ```
284
-
360
+
285
361
  ### Nesting providers
286
-
287
- Providers can be nested. The nearest one wins:
288
-
362
+
363
+ The nearest provider wins:
364
+
289
365
  ```tsx
290
366
  <TypographyProvider theme={{ font: "Bricolage Grotesque", color: "#111" }}>
291
367
  <Typography variant="H1">Uses Bricolage Grotesque</Typography>
292
-
368
+
293
369
  <TypographyProvider theme={{ font: "Playfair Display", accentColor: "#e11d48" }}>
294
370
  <Typography variant="Display">
295
371
  Uses Playfair Display with red accent
@@ -298,12 +374,157 @@ Providers can be nested. The nearest one wins:
298
374
  </TypographyProvider>
299
375
  ```
300
376
 
377
+ ---
378
+
379
+ ## Examples
380
+
381
+ ### Overline + Display combo
382
+
383
+ ```tsx
384
+ <Typography variant="Overline" color="#6366f1">New Feature</Typography>
385
+ <Typography variant="Display" font="Bricolage Grotesque" animation="clip" accentColor="#6366f1">
386
+ Build faster with <em>types</em>
387
+ </Typography>
388
+ ```
389
+
390
+ ### Body + Caption
391
+
392
+ ```tsx
393
+ <Typography variant="Body" font="Lora">
394
+ A well-set paragraph in a refined serif font brings reading pleasure.
395
+ </Typography>
396
+ <Typography variant="Caption" color="#888">
397
+ Fig. 1 — System architecture overview
398
+ </Typography>
399
+ ```
400
+
401
+ ### Truncation
402
+
403
+ ```tsx
404
+ <Typography variant="H2" truncate>
405
+ This very long title will be cut off with an ellipsis
406
+ </Typography>
407
+
408
+ <Typography variant="Body" maxLines={3}>
409
+ This paragraph will show at most three lines...
410
+ </Typography>
411
+ ```
412
+
413
+ ### Override HTML tag
414
+
415
+ ```tsx
416
+ <Typography variant="H2" as="div">
417
+ Renders as a div, styled as H2
418
+ </Typography>
419
+ ```
420
+
421
+ ---
422
+
423
+ ## Pre-loading fonts
424
+
425
+ To avoid FOUT (flash of unstyled text), pre-load fonts at the top of your app:
426
+
427
+ ```tsx
428
+ import { preloadFonts } from "@edwinvakayil/calligraphy";
429
+
430
+ // In your _app.tsx / main.tsx / layout.tsx
431
+ preloadFonts(["Bricolage Grotesque", "Instrument Serif", "DM Sans"]);
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Recommended hero font pairings
437
+
438
+ | Heading font | Style | Works well for |
439
+ |-----------------------|------------|------------------------------|
440
+ | `Bricolage Grotesque` | Bold sans | Startups, SaaS, modern brand |
441
+ | `Syne` | Geometric | Creative, portfolio, agency |
442
+ | `Fraunces` | Serif | Editorial, luxury, fashion |
443
+ | `Bebas Neue` | Condensed | Sports, bold campaigns |
444
+ | `Playfair Display` | Serif | Journalism, books, culture |
445
+ | `Outfit` | Clean sans | Apps, dashboards, fintech |
446
+
447
+ ---
448
+
449
+ ## SSR & Next.js
450
+
451
+ `@edwinvakayil/calligraphy` is fully SSR-safe. All DOM work (`<link>` and `<style>` injection) happens inside `useInsertionEffect`, which React never calls on the server. The text renders in the server HTML; animations play after hydration.
452
+
453
+ ### Next.js App Router
454
+
455
+ ```tsx
456
+ // app/layout.tsx
457
+ import { TypographyProvider } from "@edwinvakayil/calligraphy";
458
+
459
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
460
+ return (
461
+ <html lang="en">
462
+ <body>
463
+ <TypographyProvider theme={{ font: "Bricolage Grotesque", accentColor: "#6366f1" }}>
464
+ {children}
465
+ </TypographyProvider>
466
+ </body>
467
+ </html>
468
+ );
469
+ }
470
+ ```
471
+
472
+ ```tsx
473
+ // app/page.tsx — Server Component
474
+ import { Typography } from "@edwinvakayil/calligraphy";
475
+
476
+ export default function Page() {
477
+ return (
478
+ <Typography variant="Display" animation="rise">
479
+ Renders on the server, <em>animates</em> on the client
480
+ </Typography>
481
+ );
482
+ }
483
+ ```
484
+
485
+ ### Next.js Pages Router
486
+
487
+ ```tsx
488
+ // pages/_app.tsx
489
+ import { TypographyProvider } from "@edwinvakayil/calligraphy";
490
+ import type { AppProps } from "next/app";
491
+
492
+ export default function App({ Component, pageProps }: AppProps) {
493
+ return (
494
+ <TypographyProvider theme={{ font: "Syne" }}>
495
+ <Component {...pageProps} />
496
+ </TypographyProvider>
497
+ );
498
+ }
499
+ ```
500
+
501
+ ### What renders where
502
+
503
+ | Prop | Server | Client |
504
+ |------|--------|--------|
505
+ | `variant`, `font`, `color`, `align`, `truncate`, `maxLines` | Inline styles in HTML | Hydrated |
506
+ | `animation` | Class name in HTML | Keyframe `<style>` injected, animation plays |
507
+ | `motionConfig` | Raw HTML (split spans present) | Animation applied after hydration |
508
+ | `motionRef` | Plain HTML | Callback fires after hydration |
509
+ | `italic` / `accentColor` | Inline styles on `<em>` | No flash on hydration |
510
+
511
+ ---
512
+
513
+ ## Build & Publish
514
+
515
+ ```bash
516
+ npm install
517
+ npm run build # outputs CJS + ESM + .d.ts to /dist
518
+ npm pack # inspect the tarball before publishing
519
+ npm publish --access public
520
+ ```
521
+
522
+ ---
523
+
301
524
  ## License
302
525
 
303
526
  Copyright (c) 2025 Edwin Vakayil. All rights reserved.
304
527
 
305
- This package is proprietary software. You may install and use it for personal
306
- or internal business purposes, but you may not copy, modify, distribute, or
307
- create derivative works from it without explicit written permission from the author.
528
+ This package is proprietary software. You may install and use it for personal or internal business purposes, but you may not copy, modify, distribute, or create derivative works from it without explicit written permission from the author.
308
529
 
309
530
  See the [LICENSE](./LICENSE) file for full terms.
@@ -10,3 +10,22 @@ export declare function applyThreadOffsets(container: HTMLElement): void;
10
10
  export declare function buildSplitHTML(animation: HeroAnimation, html: string): string;
11
11
  export declare const buildStaggerHTML: (html: string) => string;
12
12
  export declare const buildLettersHTML: (html: string) => string;
13
+ /**
14
+ * Injects a one-off @keyframes rule with a generated name and returns that name.
15
+ * Deduped by a hash of the keyframe body so the same keyframe is only injected once.
16
+ */
17
+ declare function injectCustomKeyframes(keyframeBody: string): string;
18
+ /**
19
+ * Builds the innerHTML for a custom motionConfig.
20
+ * - split "none" → returns the raw html unchanged; caller applies class to element
21
+ * - split "words" → wraps each word in a span with the animation + stagger delay
22
+ * - split "chars" → wraps each character in a span with the animation + stagger delay
23
+ *
24
+ * Returns { html, animationValue } where animationValue is the full CSS animation
25
+ * shorthand to set on each span (or on the element itself when split is "none").
26
+ */
27
+ export declare function buildCustomHTML(html: string, keyframeName: string, duration: string, easing: string, delay: string, fillMode: string, split: "none" | "words" | "chars", staggerDelay: number): {
28
+ html: string;
29
+ baseAnimation: string;
30
+ };
31
+ export { injectCustomKeyframes };