gclass-anims 1.0.0-beta.23 → 1.0.0-beta.24

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/AnimToggle.js CHANGED
@@ -2,7 +2,13 @@
2
2
  import { defaults, animations } from './Config.js'
3
3
  import { gsap } from 'gsap'
4
4
  import { GSDevTools } from 'gsap/all'
5
- gsap.registerPlugin(GSDevTools)
5
+ // GSDevTools is intentionally NOT registered at module scope. This module is
6
+ // imported by index.js, and package.json declares "sideEffects": false-a
7
+ // bundler is then free to drop the registration, leaving gclassDev() to call
8
+ // GSDevTools.create() against an unregistered plugin. It is also unnecessary:
9
+ // gclassDev() is usable standalone (the docs site and dev-react-strict both
10
+ // call it without ever calling initAnimations()), so registration must happen
11
+ // at the point of use instead of at import time.
6
12
  // localStorage key controlling whether the GSAP animation system is mounted.
7
13
  const STORAGE_KEY = 'gclass-animations-enabled'
8
14
  // localStorage key for a forced reduced-motion override (see
@@ -47,7 +53,7 @@ function readReducedOverride() {
47
53
  // - Otherwise, if the user HAS an explicit stored choice, respect it
48
54
  // (override wins), even under reduced motion.
49
55
  // - Otherwise (no stored value) fall back to the default, which is ON unless
50
- // reduced motion is detected - in which case animations are off.
56
+ // reduced motion is detected-in which case animations are off.
51
57
  function getEnabled() {
52
58
  if (forcedReduced) return false
53
59
  return stored === null ? !reduced : stored
@@ -119,7 +125,7 @@ let bootTimeout = null
119
125
  let bootStyle = null
120
126
  let hasBooted = false // true after first hard-load boot, skips boot on SPA path changes (remains false until first boot, resets on hard reload)
121
127
 
122
- // Runtime config for gclassOpts — 0 = defaults (no throttle, default GSAP ticker)
128
+ // Runtime config for gclassOpts-0 = defaults (no throttle, default GSAP ticker)
123
129
  let currentThrottle = 0
124
130
  let currentFps = 0
125
131
  const configSubscribers = new Set()
@@ -145,7 +151,7 @@ function normalizeGclassArgs(throttlePerFrame, fps) {
145
151
 
146
152
  /**
147
153
  * Change GClass runtime options on the fly without reload.
148
- * gclassOpts(throttlePerFrame, fps) — both optional numbers.
154
+ * gclassOpts(throttlePerFrame, fps)-both optional numbers.
149
155
  * gclassOpts() or gclassOpts(undefined, undefined) resets to defaults (no throttle, default ticker).
150
156
  * Also accepts gclassOpts({throttlePerFrame, fps}).
151
157
  * Example low-end button: onClick={() => gclassOpts(1, 30)}
@@ -187,6 +193,8 @@ const readBootTime = (els, fallback) => {
187
193
  * Accepts: gclassDev() | gclassDev("width:50%; bottom:30px") | gclassDev({width:"50%"}) | gclassDev({css:{}, minimal:true, animation:tl})
188
194
  */
189
195
  export function gclassDev(cssOrOpts){
196
+ // Register at point of use-see the note at the top of this file.
197
+ gsap.registerPlugin(GSDevTools)
190
198
  const docCss = {
191
199
  backgroundColor: "rgba(15,23,42,0.96)", // slate-900 ~ doc bg-slate-950/900
192
200
  border: "1px solid rgba(51,65,85,0.8)", // ring-slate-700 Header.jsx:8
@@ -223,12 +231,12 @@ export function gclassDev(cssOrOpts){
223
231
  // or reduced-motion fallback with no explicit choice). Idempotent: calling it
224
232
  // again tears down any previous run first.
225
233
  // Now also handles boot screen: any HTML/JSX with `.boot-up` anywhere is treated as the boot overlay.
226
- // No separate initBoot needed - just call initAnimations().
234
+ // No separate initBoot needed-just call initAnimations().
227
235
  // Boot stops all DOM rendering for defaults.bootTime (overwritten by boot-time-N class).
228
236
  // throttlePerFrame / fps: forwarded to gclassOpts-equivalent runtime.
229
- // initAnimations(throttlePerFrame, fps) — positional numbers, 0/undefined = defaults
230
- // initAnimations({throttlePerFrame, fps}) — object overload
231
- // initAnimations() — uses last gclassOpts values (defaults on first call)
237
+ // initAnimations(throttlePerFrame, fps)-positional numbers, 0/undefined = defaults
238
+ // initAnimations({throttlePerFrame, fps})-object overload
239
+ // initAnimations()-uses last gclassOpts values (defaults on first call)
232
240
  export function initAnimations(throttlePerFrame, fps) {
233
241
  // gclassOpts-style normalization: (throttle, fps) positional or {throttlePerFrame, fps} object
234
242
  // no args -> fallback to last gclassOpts values (defaults 0 = no throttle, default ticker)
@@ -248,9 +256,9 @@ export function initAnimations(throttlePerFrame, fps) {
248
256
  else gsap.ticker.fps(0)
249
257
  }
250
258
  if (typeof window === 'undefined' || !getEnabled()) return
251
- // boot already in progress (first mount in StrictMode) - ignore second mount
259
+ // boot already in progress (first mount in StrictMode)-ignore second mount
252
260
  if (bootTimeout) {
253
- console.log(`[initAnimations] boot already in progress - ignoring duplicate call`)
261
+ console.log(`[initAnimations] boot already in progress-ignoring duplicate call`)
254
262
  return
255
263
  }
256
264
  if (cleanup) { cleanup(); cleanup = null }
@@ -258,7 +266,7 @@ export function initAnimations(throttlePerFrame, fps) {
258
266
  if (bootStyle) { bootStyle.remove(); bootStyle = null; document.documentElement.classList.remove('gclass-booting') }
259
267
 
260
268
  const hideBootEls = (els) => {
261
- // React-safe: don't el.remove() - React owns the nodes and will throw
269
+ // React-safe: don't el.remove()-React owns the nodes and will throw
262
270
  // insertBefore/removeChild on next commit if we mutate outside React.
263
271
  // Hiding keeps React's tree intact but visually removes boot screen.
264
272
  els.forEach(el => {
@@ -272,12 +280,12 @@ export function initAnimations(throttlePerFrame, fps) {
272
280
  if (!bootEls.length) {
273
281
  // no .boot-up -> completely skip boot
274
282
  } else if (bootEls.length > 1) {
275
- console.error(`[initAnimations] Multiple .boot-up elements detected (${bootEls.length}) - skipping all boot animations`, bootEls)
283
+ console.error(`[initAnimations] Multiple .boot-up elements detected (${bootEls.length})-skipping all boot animations`, bootEls)
276
284
  hideBootEls(bootEls)
277
285
  hasBooted = true
278
286
  // fall through to normal initListeners without pausing DOM
279
287
  } else if (hasBooted && !bootTimeout) {
280
- // path change after already booted (SPA navigation) - skip boot, hard reload resets hasBooted
288
+ // path change after already booted (SPA navigation)-skip boot, hard reload resets hasBooted
281
289
  console.log(`[initAnimations] skipping boot on path change (already booted)`, bootEls)
282
290
  hideBootEls(bootEls)
283
291
  hasBooted = true
@@ -303,7 +311,7 @@ export function initAnimations(throttlePerFrame, fps) {
303
311
  // ensure boot els are visible even if nested inside hidden ancestors
304
312
  bootEls.forEach(el => { el.style.visibility = 'visible' })
305
313
 
306
- // animations inside boot screen must play while rest of DOM is hidden - init scoped to boot-up
314
+ // animations inside boot screen must play while rest of DOM is hidden-init scoped to boot-up
307
315
  bootCleanup = initListeners(bootEl, effThrottle)
308
316
 
309
317
  bootTimeout = setTimeout(() => {
@@ -338,7 +346,7 @@ export function initAnimations(throttlePerFrame, fps) {
338
346
  }
339
347
 
340
348
  if (!cfg || !from) {
341
- console.warn(`[initAnimations] boot-end-${name} has no from state - removing without animation`)
349
+ console.warn(`[initAnimations] boot-end-${name} has no from state-removing without animation`)
342
350
  finish()
343
351
  return
344
352
  }
package/Animations.js CHANGED
@@ -2,12 +2,32 @@ import { DrawSVGPlugin, Flip, MotionPathPlugin, ScrambleTextPlugin, SplitText, T
2
2
  import { gsap } from "gsap";
3
3
  import { defaults } from './Config.js'
4
4
 
5
- gsap.registerPlugin(Flip)
6
- gsap.registerPlugin(SplitText)
7
- gsap.registerPlugin(TextPlugin)
8
- gsap.registerPlugin(DrawSVGPlugin)
9
- gsap.registerPlugin(MotionPathPlugin)
10
- gsap.registerPlugin(ScrambleTextPlugin)
5
+ // Plugins are deliberately NOT registered here at module scope.
6
+ //
7
+ // package.json declares "sideEffects": false, which tells a consumer's
8
+ // bundler that importing this module has no observable side effects and that
9
+ // it may be dropped entirely when nothing is imported from it. Registering
10
+ // plugins on import contradicts that promise: a bundler that tree-shakes this
11
+ // module away silently un-registers SplitText / DrawSVG / ScrambleText and the
12
+ // corresponding classes then throw at runtime, with a stack trace that points
13
+ // nowhere useful.
14
+ //
15
+ // Registration is therefore deferred to registerPlugins() below, which
16
+ // initListeners() calls (Listeners.js). This module stays safe to import for
17
+ // side-effect-free helpers, and the whole set is registered exactly once.
18
+ let pluginsRegistered = false
19
+ export function registerPlugins() {
20
+ if (pluginsRegistered) return
21
+ pluginsRegistered = true
22
+ gsap.registerPlugin(
23
+ Flip,
24
+ SplitText,
25
+ TextPlugin,
26
+ DrawSVGPlugin,
27
+ MotionPathPlugin,
28
+ ScrambleTextPlugin,
29
+ )
30
+ }
11
31
 
12
32
  // --- Breakpoint helpers for modifiers (xs/s/m/l/xl) -------------------------
13
33
  // Lazy to avoid circular init (Config.js <-> Animations.js)
@@ -322,7 +342,14 @@ export function splitPaths (paths){
322
342
  const path = toSplit[0]
323
343
  if (!path) return newPaths
324
344
  if (path._gcSplitPaths?.[0]?.isConnected) return path._gcSplitPaths
345
+ // Only SVG <path> elements can be split. A non-path target (or a detached
346
+ // node) makes MotionPathPlugin.getRawPath return undefined, and the
347
+ // .map() below then throws and takes the whole engine init down with it.
348
+ // Bail out quietly instead: draw-split is a decorative extra and a
349
+ // misapplied class must never be fatal.
350
+ if (path.nodeType !== 1 || path.tagName?.toLowerCase() !== "path" || !path.isConnected) return newPaths
325
351
  const rawPath = MotionPathPlugin.getRawPath(path)
352
+ if (!rawPath || !Array.isArray(rawPath)) return newPaths
326
353
  const parent = path.parentNode
327
354
  const attributes = [...path.attributes]
328
355
  newPaths = rawPath.map(segment => {
package/CHANGELOG.md CHANGED
@@ -2,24 +2,49 @@
2
2
 
3
3
  All notable changes to `gclass-anims` will be documented in this file.
4
4
 
5
+ ## [1.0.0-beta.24] - 2026-10-05
6
+ ### Added
7
+ - **`.gc-tween-[...]` escape hatch** - write GSAP vars inline when the menu has no class for it. `class="gc-tween-[y:40,opacity:0]"` plays **from** those values **to** the element's live resting state (read via `getComputedStyle`), so end values follow CSS / dark mode / breakpoints instead of being frozen into the class. Multiple `gc-tween-[...]` classes on one element **merge** (both properties end up on one tween). Modifiers work as usual: `.time-N`, `.ease-*`, `.order`, `.priority-N`, `.delay-N`, `.reduced`, `.preserve`, and `bp:` gating (`m:gc-tween-[...]`). Trigger prefixes mirror the `css-*` grammar: `hover-gc-tween-[...]`, `click-gc-tween-[...]`.
8
+ - **`.gc-tl` timeline host + `.gc-<n>-[...]` ordered steps** - `class="gc-tl gc-1-[y:40] gc-2-[opacity:1] gc-3-[scale:0.8]"` builds a `gsap.timeline()` with one tween per step, sorted ascending by the numeric prefix regardless of attribute order. Numbered steps imply a timeline, so `.gc-tl` is optional but expresses intent.
9
+ - **Per-step control keys inside the bracket** - `time:` / `dur:` overrides that step's duration (class-level `.time-N` still sets the default for steps that don't), `ease:` overrides the step's ease, and `at:` sets the GSAP **position** parameter (`at:0`, `at:">"`, `at:"+=0.2"`, `at:"<"`) for overlapping steps. These are consumed by the engine and never reach GSAP as properties.
10
+ - `.delay-N` is now a real modifier-a plain additive offset applied **after** the `.order` / `.priority-N` delay, so `order delay-2` shifts the stagger and `priority-3 delay-1` composes. `Config.js:23` previously promised `delay` support that no code implemented.
11
+ - `registerPlugins()` is exported, so a consumer can register the plugin set explicitly without booting the engine.
12
+
13
+ ### Fixed
14
+ - **Consumer-package corruption via `sideEffects: false`.** All GSAP plugin registration moved out of module scope into `initListeners()` (`registerPlugins()` in `Animations.js`, plus the existing `TextPlugin`/`ScrollTrigger`/`SplitText` call). `package.json` declares `sideEffects: false`, which tells a consumer's bundler it may drop a module that only registers plugins on import-silently un-registering `SplitText` / `DrawSVG` / `ScrambleText` and making the matching classes throw at runtime inside someone else's app. Importing the package for helpers is now genuinely side-effect free. This also removes the duplicate `SplitText` / `TextPlugin` registration that existed at both module scope and init time.
15
+ - **`gclassDev()` no longer depends on import order.** `GSDevTools` registration moved from `AnimToggle.js` module scope into the body of `gclassDev()`, so it works whether or not `initAnimations()` has run. The docs site and `dev-react-strict` both call `gclassDev()` standalone and were relying on the module-scope registration.
16
+ - **Breakpoint tables are no longer frozen at import.** `Listeners.js` computed `bpNames` / `bpMap` / `bpPrefixRE` as module-scope `const`s, so assigning `defaults.breakpoints` after import was silently ignored. They are now derived lazily and memoised on the identity of the `defaults.breakpoints` object (port of the lazy `getBpDataMod()` approach `Animations.js` already used).
17
+ - **`css-<prop>-<from>-<to>` now merges instead of first-match-wins.** `parseCssAnim` returned on the first matching class, so an element could carry exactly one `css-*` animation and any further classes were ignored. All matching classes now merge into a single tween; a later class for the same property wins, and the first trigger prefix seen decides spawn/hover/click/loop for the whole element.
18
+ - **`.appear` on dynamically inserted `.gc` elements.** `setupGcAnims` is registered in the MutationObserver appear batch as well as the initial load pass, matching `setupCssAnims`.
19
+
20
+ ### Changed
21
+ - `dist/` is now minified with source maps (`minify: true`, `sourcemap: true` in `vite.lib.config.js`). ESM 95.7 kB → 74.7 kB (gzip 19.6 kB), CJS 97.7 kB → 57.7 kB (gzip 17.5 kB). Source maps keep stack traces resolvable to the original `Listeners.js` / `Animations.js` lines. Note: `minify: 'esbuild'` fails on a clean install here because this repo has no direct `esbuild` dependency (Vite 8 delegates to rolldown)-`minify: true` is the portable form.
22
+ - **Removed `defaults.textStagger`.** Declared in `Config.js`, typed in `index.d.ts`, and documented on the defaults page, but read by nothing-stagger is derived arithmetically in `playText`. Deleted from all three rather than wired up, because making it the default would silently change the timing of every existing text animation.
23
+ - `index.d.ts` drift fixed: `initListeners` was declared as a **default** export while `index.js` re-exports it **named**, so `import initListeners from 'gclass-anims'` type-checked and then returned `undefined` at runtime; `Example` was typed but never exported (`index.js` omits it) and is now removed; `Defaults` now declares `breakpoints`, without which `defaults.breakpoints = {...}` was a type error and the whole `m:` system was unconfigurable in TypeScript; `finalOpacity`, `stashText`, `spawnClipReveal`, `curtainHorizontal`, `curtainVertical` and `scrambleSegments` now have declarations (all six ship in the bundle).
24
+
25
+ ### Notes for consumers
26
+ - **One bracket group per `.gc` class.** Tailwind re-lexes bracket contents as standalone candidates, so a second bracket group leaks real CSS into your stylesheet: `gc-tween-[y:40]` emits nothing, but `gc-tween-[y:40]-[y:0]` makes Tailwind emit `.[y:0] { y: 0 }`. The parser only ever accepts one group, and `dev-react-strict/src/__tests__/gc-tween.test.jsx` asserts this against real Tailwind (with a control case proving the harness can detect a leak at all). For a two-value tween use two numbered steps, or the existing `css-<prop>-<from>-<to>`.
27
+ - `.randomize-*` does not reach `.gc` tweens-it is injected by monkey-patching `gsap.fromTo` inside `play()`-built tweens only.
28
+ - `demo/` is legacy: those static pages predate the docs site and are not wired into anything. Their `gclass-bundle.js` is a stale prebuilt bundle (dated 2026-08-27) and nothing in the repo regenerates it, so treat that directory as historical rather than a live example. `dist/` is the current build.
29
+
5
30
  ## [1.0.0-beta.23] - 2026-09-09
6
- - Added `gclassDev()` helper — `AnimToggle.js:185` `gclassDev(cssOrOpts)` wraps `GSDevTools.create()` with doc-styled defaults (`slate-900` `rgba(15,23,42,0.96)`, `slate-700` border, `rounded-xl`, `backdrop-blur`). Accepts `gclassDev()` (defaults), `gclassDev("width:50%; bottom:30px")` (CSS string), `gclassDev({width:"50%"})` (style object shorthand), or `gclassDev({css, animation, minimal, ...})` (full `GSDevTools` opts). Registers `GSDevTools` via `gsap.registerPlugin(GSDevTools)` (`AnimToggle.js:4`), re-exported from `index.js:1` and typed in `index.d.ts:35`.
31
+ - Added `gclassDev()` helper-`AnimToggle.js:185` `gclassDev(cssOrOpts)` wraps `GSDevTools.create()` with doc-styled defaults (`slate-900` `rgba(15,23,42,0.96)`, `slate-700` border, `rounded-xl`, `backdrop-blur`). Accepts `gclassDev()` (defaults), `gclassDev("width:50%; bottom:30px")` (CSS string), `gclassDev({width:"50%"})` (style object shorthand), or `gclassDev({css, animation, minimal, ...})` (full `GSDevTools` opts). Registers `GSDevTools` via `gsap.registerPlugin(GSDevTools)` (`AnimToggle.js:4`), re-exported from `index.js:1` and typed in `index.d.ts:35`.
7
32
 
8
33
  ## [1.0.0-beta.22.2] - 2026-09-06
9
- - Fixed `m:order` (and any `bp:order` / `bp:priority-*` / `bp:ease-*` / `bp:time-*`) disabling the entire `scroll` animation on smaller screens — `Listeners.js:1200,1232,1262` previously wrapped `setupScroll` / `playText` scroll variants in generic `runWithBreakpoint(el, ...)` which gated on *any* `bp:*` class on the element (so `m:order` gated `scroll`). Now uses per-animation `qAllAllVariants` + `runWithBreakpointForSel` (`".scroll"`, `".scroll-progress"`, `tSel`) so only a true `m:scroll` / `m:spawn-*` gates its own animation; unrelated modifiers like `m:order` only affect `hasGClass("order")` / `readTiming` (stagger) and `spawn-*` + `scroll` still play on all sizes (without stagger on <m). Keeps live `gsap.matchMedia` handling for true breakpoint-gated scroll. Reverts `HowWorkSection.jsx` workaround from `m:order` back to plain `order` for the default staggered landing.
34
+ - Fixed `m:order` (and any `bp:order` / `bp:priority-*` / `bp:ease-*` / `bp:time-*`) disabling the entire `scroll` animation on smaller screens-`Listeners.js:1200,1232,1262` previously wrapped `setupScroll` / `playText` scroll variants in generic `runWithBreakpoint(el, ...)` which gated on *any* `bp:*` class on the element (so `m:order` gated `scroll`). Now uses per-animation `qAllAllVariants` + `runWithBreakpointForSel` (`".scroll"`, `".scroll-progress"`, `tSel`) so only a true `m:scroll` / `m:spawn-*` gates its own animation; unrelated modifiers like `m:order` only affect `hasGClass("order")` / `readTiming` (stagger) and `spawn-*` + `scroll` still play on all sizes (without stagger on <m). Keeps live `gsap.matchMedia` handling for true breakpoint-gated scroll. Reverts `HowWorkSection.jsx` workaround from `m:order` back to plain `order` for the default staggered landing.
10
35
 
11
36
  ## [1.0.0-beta.22.1] - 2026-09-06
12
- - Fixed `.order` stagger grouping bug — `Listeners.js:76` `wrapQAll` now preserves DOM order via `body *` filter + `elementMatchesSel` (was `Set([...spawn-up], [...spawn-down])` grouping by type, now top-to-bottom as `qAll` does). Fixes `doc` `spawn` and landing `order` appearing all over.
37
+ - Fixed `.order` stagger grouping bug-`Listeners.js:76` `wrapQAll` now preserves DOM order via `body *` filter + `elementMatchesSel` (was `Set([...spawn-up], [...spawn-down])` grouping by type, now top-to-bottom as `qAll` does). Fixes `doc` `spawn` and landing `order` appearing all over.
13
38
  - No API change from `beta.22`.
14
39
 
15
40
  ## [1.0.0-beta.22] - 2026-09-06
16
- - Added responsive breakpoints — `Config.js:75` `defaults.breakpoints {xs:475,s:640,m:768,l:1024,xl:1280}` (single-letter `s/m/l` avoids Tailwind `sm/md/lg` collision). Usage `m:spawn-up`, `l:float`, `xs:spawn-up`. Gating is live via `gsap.matchMedia` (`Listeners.js:26,202`).
17
- - Added breakpoint-aware modifiers — `m:amount-20`, `m:time-2`, `m:ease-bounce`, `m:priority-3`, `m:chars-[...]`, `m:spawn-num-10` etc. Mobile-first largest active wins (`Listeners.js:237` `getActivePrefixedClass`, `Animations.js:13` lazy helpers). Covers `amount-`, `time-`, `priority-`, `edelay-`, `etime-`, `stagger-`, `fill-time-`, `reveal-delay-`, `spawn-num-`, `progress-start-`, etc.
18
- - Fixed runtime `customAnims` re-normalization — `Listeners.js:190` now calls `normalize(customAnims)` inside `initListeners()` so `customAnims.push()` before next `init` is picked up.
19
- - Added ESM + CJS dual build — `vite.lib.config.js` (Vite lib) builds `dist/gclass.esm.js` + `dist/gclass.cjs` (`gsap` external); `package.json:4` bumped to `beta.22`, `main/module` point to `dist/`, `exports: {import, require}`, `sideEffects:false`, `prepublishOnly: build`.
20
- - Fixed CJS `gsap` interop — `Animations.js:2`/`Listeners.js:1`/`AnimToggle.js:3`/`CustomAnims.js:1` now `import {gsap} from 'gsap'` (named import) for correct `require('gsap').gsap` interop.
21
- - Added `dev-react-strict` test harness — `vitest` + `jsdom` + `src/__tests__/breakpoints.test.jsx` (14 tests: gating, modifiers, Tailwind coexistence, StrictMode) + `BreakpointHarness.jsx` visual; `vite.config.js` test config.
22
- - Documented breakpoints — `doc/src/app/documentation/responsive-design/page.js` (was duplicate `optimization`).
41
+ - Added responsive breakpoints-`Config.js:75` `defaults.breakpoints {xs:475,s:640,m:768,l:1024,xl:1280}` (single-letter `s/m/l` avoids Tailwind `sm/md/lg` collision). Usage `m:spawn-up`, `l:float`, `xs:spawn-up`. Gating is live via `gsap.matchMedia` (`Listeners.js:26,202`).
42
+ - Added breakpoint-aware modifiers-`m:amount-20`, `m:time-2`, `m:ease-bounce`, `m:priority-3`, `m:chars-[...]`, `m:spawn-num-10` etc. Mobile-first largest active wins (`Listeners.js:237` `getActivePrefixedClass`, `Animations.js:13` lazy helpers). Covers `amount-`, `time-`, `priority-`, `edelay-`, `etime-`, `stagger-`, `fill-time-`, `reveal-delay-`, `spawn-num-`, `progress-start-`, etc.
43
+ - Fixed runtime `customAnims` re-normalization-`Listeners.js:190` now calls `normalize(customAnims)` inside `initListeners()` so `customAnims.push()` before next `init` is picked up.
44
+ - Added ESM + CJS dual build-`vite.lib.config.js` (Vite lib) builds `dist/gclass.esm.js` + `dist/gclass.cjs` (`gsap` external); `package.json:4` bumped to `beta.22`, `main/module` point to `dist/`, `exports: {import, require}`, `sideEffects:false`, `prepublishOnly: build`.
45
+ - Fixed CJS `gsap` interop-`Animations.js:2`/`Listeners.js:1`/`AnimToggle.js:3`/`CustomAnims.js:1` now `import {gsap} from 'gsap'` (named import) for correct `require('gsap').gsap` interop.
46
+ - Added `dev-react-strict` test harness-`vitest` + `jsdom` + `src/__tests__/breakpoints.test.jsx` (14 tests: gating, modifiers, Tailwind coexistence, StrictMode) + `BreakpointHarness.jsx` visual; `vite.config.js` test config.
47
+ - Documented breakpoints-`doc/src/app/documentation/responsive-design/page.js` (was duplicate `optimization`).
23
48
 
24
49
  ## [1.0.0-beta.21] - 2026-9-3
25
50
  - Added a `gclassOpts()` function that controls the animation fps and observer throttling
@@ -51,22 +76,22 @@ All notable changes to `gclass-anims` will be documented in this file.
51
76
 
52
77
  ## [1.0.0-beta.12] - 2026-08-26
53
78
 
54
- - Fixed `scramble` with `scroll-progress` throwing `can't convert undefined to object` - `computeTo` (`Listeners.js:424`) and scrub `to` builder (`Listeners.js:867`) now guard `from` (`scramble` has no `from`).
79
+ - Fixed `scramble` with `scroll-progress` throwing `can't convert undefined to object`-`computeTo` (`Listeners.js:424`) and scrub `to` builder (`Listeners.js:867`) now guard `from` (`scramble` has no `from`).
55
80
 
56
81
  ## [1.0.0-beta.11] - 2026-08-26
57
82
 
58
- - Fixed `.draw-split` infinite loop when paired with `.appear` - `splitPaths` (`Animations.js:244`) now strips `appear`/`scroll`/`scroll-progress`/`draw`/`draw-split`/`data-gsap-*` from cloned segments, marks children with `data-gsap-split` + `contain:paint`/`will-change:transform` isolation, and prevents `appearObserver` (`Listeners.js:1559`) re-triggering. Also isolated `draw-split` demos in docs.
83
+ - Fixed `.draw-split` infinite loop when paired with `.appear`-`splitPaths` (`Animations.js:244`) now strips `appear`/`scroll`/`scroll-progress`/`draw`/`draw-split`/`data-gsap-*` from cloned segments, marks children with `data-gsap-split` + `contain:paint`/`will-change:transform` isolation, and prevents `appearObserver` (`Listeners.js:1559`) re-triggering. Also isolated `draw-split` demos in docs.
59
84
 
60
85
  ## [1.0.0-beta.10] - 2026-08-26
61
86
 
62
- - Added `.randomize-<prop>-[min]-[max]` - randomize spawn start values per element (e.g. `randomize-rotation-[-90]-[90]`, `randomize-x-[-40]-[40]`). Re-rolls on every replay (`.scroll` re-enter, `.appear`).
63
- - Added `.draw` - stroke-draw reveal for SVG paths using DrawSVGPlugin (`drawSVG: 0% → 100%`).
64
- - Added `.draw-split` - draws multi-segment SVG paths sequentially at constant pen speed (splits paths with multiple `M` commands into individual strokes).
65
- - Added `.scramble` - text resolves from empty through scrambled characters into real content (ScrambleTextPlugin). Supports `.reveal-delay-N`, `.chars-[...]`, `.amount-N`, `.scramble-rtl`.
66
- - Added `.scramble-all` - variant of scramble with no empty start; the finished string flips to garbage as a whole then sweeps back.
67
- - Added `.scroll-frame` - use a scrollable container as the ScrollTrigger scroller for nested `.scroll` / `.scroll-progress` elements (innermost `.scroll-frame` ancestor wins).
68
- - Fixed `spawn-text-*` (SplitText) not working correctly on flex containers - text runs are now wrapped in block containers before splitting to preserve flex layout, spacing, and line grouping.
87
+ - Added `.randomize-<prop>-[min]-[max]`-randomize spawn start values per element (e.g. `randomize-rotation-[-90]-[90]`, `randomize-x-[-40]-[40]`). Re-rolls on every replay (`.scroll` re-enter, `.appear`).
88
+ - Added `.draw`-stroke-draw reveal for SVG paths using DrawSVGPlugin (`drawSVG: 0% → 100%`).
89
+ - Added `.draw-split`-draws multi-segment SVG paths sequentially at constant pen speed (splits paths with multiple `M` commands into individual strokes).
90
+ - Added `.scramble`-text resolves from empty through scrambled characters into real content (ScrambleTextPlugin). Supports `.reveal-delay-N`, `.chars-[...]`, `.amount-N`, `.scramble-rtl`.
91
+ - Added `.scramble-all`-variant of scramble with no empty start; the finished string flips to garbage as a whole then sweeps back.
92
+ - Added `.scroll-frame`-use a scrollable container as the ScrollTrigger scroller for nested `.scroll` / `.scroll-progress` elements (innermost `.scroll-frame` ancestor wins).
93
+ - Fixed `spawn-text-*` (SplitText) not working correctly on flex containers-text runs are now wrapped in block containers before splitting to preserve flex layout, spacing, and line grouping.
69
94
 
70
- ## [1.0.0-beta.9] - Previous release
95
+ ## [1.0.0-beta.9]-Previous release
71
96
 
72
97
  - See git history for earlier changes.
package/Config.js CHANGED
@@ -66,7 +66,6 @@ export const defaults = {
66
66
  effectOffset: 20,
67
67
  progressStart: "top bottom",
68
68
  progressEnd: "center center",
69
- textStagger: 0.03,
70
69
  typewriterSplitCharDuration: 0.05,
71
70
  minTextPartDuration: 0.3,
72
71
  revealDelay:0,