gclass-anims 1.0.0-beta.22.2 → 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
@@ -1,7 +1,14 @@
1
1
  import initListeners from './Listeners.js'
2
2
  import { defaults, animations } from './Config.js'
3
3
  import { gsap } from 'gsap'
4
-
4
+ import { GSDevTools } from 'gsap/all'
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.
5
12
  // localStorage key controlling whether the GSAP animation system is mounted.
6
13
  const STORAGE_KEY = 'gclass-animations-enabled'
7
14
  // localStorage key for a forced reduced-motion override (see
@@ -46,7 +53,7 @@ function readReducedOverride() {
46
53
  // - Otherwise, if the user HAS an explicit stored choice, respect it
47
54
  // (override wins), even under reduced motion.
48
55
  // - Otherwise (no stored value) fall back to the default, which is ON unless
49
- // reduced motion is detected - in which case animations are off.
56
+ // reduced motion is detected-in which case animations are off.
50
57
  function getEnabled() {
51
58
  if (forcedReduced) return false
52
59
  return stored === null ? !reduced : stored
@@ -118,7 +125,7 @@ let bootTimeout = null
118
125
  let bootStyle = null
119
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)
120
127
 
121
- // Runtime config for gclassOpts — 0 = defaults (no throttle, default GSAP ticker)
128
+ // Runtime config for gclassOpts-0 = defaults (no throttle, default GSAP ticker)
122
129
  let currentThrottle = 0
123
130
  let currentFps = 0
124
131
  const configSubscribers = new Set()
@@ -144,7 +151,7 @@ function normalizeGclassArgs(throttlePerFrame, fps) {
144
151
 
145
152
  /**
146
153
  * Change GClass runtime options on the fly without reload.
147
- * gclassOpts(throttlePerFrame, fps) — both optional numbers.
154
+ * gclassOpts(throttlePerFrame, fps)-both optional numbers.
148
155
  * gclassOpts() or gclassOpts(undefined, undefined) resets to defaults (no throttle, default ticker).
149
156
  * Also accepts gclassOpts({throttlePerFrame, fps}).
150
157
  * Example low-end button: onClick={() => gclassOpts(1, 30)}
@@ -181,16 +188,55 @@ const readBootTime = (els, fallback) => {
181
188
  return max ?? fallback
182
189
  }
183
190
 
191
+ /**
192
+ * Create GSDevTools UI styled like doc/ (Header.jsx:8 slate-800/50 ring + layout.js:85 slate-950).
193
+ * Accepts: gclassDev() | gclassDev("width:50%; bottom:30px") | gclassDev({width:"50%"}) | gclassDev({css:{}, minimal:true, animation:tl})
194
+ */
195
+ export function gclassDev(cssOrOpts){
196
+ // Register at point of use-see the note at the top of this file.
197
+ gsap.registerPlugin(GSDevTools)
198
+ const docCss = {
199
+ backgroundColor: "rgba(15,23,42,0.96)", // slate-900 ~ doc bg-slate-950/900
200
+ border: "1px solid rgba(51,65,85,0.8)", // ring-slate-700 Header.jsx:8
201
+ borderRadius: "12px", // rounded-xl
202
+ boxShadow: "0 0 0 1px rgba(51,65,85,0.5), 0 8px 32px rgba(0,0,0,0.45)",
203
+ backdropFilter: "blur(8px)",
204
+ color: "#e2e8f0", // slate-200
205
+ bottom: "16px",
206
+ width: "92%",
207
+ maxWidth: "860px",
208
+ left: "50%",
209
+ transform: "translateX(-50%)",
210
+ }
211
+ let opts = {}
212
+ let css = docCss
213
+ if (typeof cssOrOpts === "string") {
214
+ css = cssOrOpts // verbatim string: "width:50%; bottom:30px"
215
+ } else if (cssOrOpts && typeof cssOrOpts === "object") {
216
+ // {width:"50%", bottom:"30px"} shorthand OR {css:{}, animation, minimal,...} full opts
217
+ const hasFullOpts = "css" in cssOrOpts || "animation" in cssOrOpts || "container" in cssOrOpts || "minimal" in cssOrOpts || "id" in cssOrOpts
218
+ if (hasFullOpts) {
219
+ const { css: userCss, ...rest } = cssOrOpts
220
+ opts = rest
221
+ if (typeof userCss === "string") css = userCss
222
+ else if (userCss && typeof userCss === "object") css = { ...docCss, ...userCss }
223
+ } else {
224
+ css = { ...docCss, ...cssOrOpts }
225
+ }
226
+ }
227
+ return GSDevTools.create({ css, ...opts })
228
+ }
229
+
184
230
  // Boots the GSAP animation system unless animations are disabled (stored "off"
185
231
  // or reduced-motion fallback with no explicit choice). Idempotent: calling it
186
232
  // again tears down any previous run first.
187
233
  // Now also handles boot screen: any HTML/JSX with `.boot-up` anywhere is treated as the boot overlay.
188
- // No separate initBoot needed - just call initAnimations().
234
+ // No separate initBoot needed-just call initAnimations().
189
235
  // Boot stops all DOM rendering for defaults.bootTime (overwritten by boot-time-N class).
190
236
  // throttlePerFrame / fps: forwarded to gclassOpts-equivalent runtime.
191
- // initAnimations(throttlePerFrame, fps) — positional numbers, 0/undefined = defaults
192
- // initAnimations({throttlePerFrame, fps}) — object overload
193
- // 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)
194
240
  export function initAnimations(throttlePerFrame, fps) {
195
241
  // gclassOpts-style normalization: (throttle, fps) positional or {throttlePerFrame, fps} object
196
242
  // no args -> fallback to last gclassOpts values (defaults 0 = no throttle, default ticker)
@@ -210,9 +256,9 @@ export function initAnimations(throttlePerFrame, fps) {
210
256
  else gsap.ticker.fps(0)
211
257
  }
212
258
  if (typeof window === 'undefined' || !getEnabled()) return
213
- // boot already in progress (first mount in StrictMode) - ignore second mount
259
+ // boot already in progress (first mount in StrictMode)-ignore second mount
214
260
  if (bootTimeout) {
215
- console.log(`[initAnimations] boot already in progress - ignoring duplicate call`)
261
+ console.log(`[initAnimations] boot already in progress-ignoring duplicate call`)
216
262
  return
217
263
  }
218
264
  if (cleanup) { cleanup(); cleanup = null }
@@ -220,7 +266,7 @@ export function initAnimations(throttlePerFrame, fps) {
220
266
  if (bootStyle) { bootStyle.remove(); bootStyle = null; document.documentElement.classList.remove('gclass-booting') }
221
267
 
222
268
  const hideBootEls = (els) => {
223
- // 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
224
270
  // insertBefore/removeChild on next commit if we mutate outside React.
225
271
  // Hiding keeps React's tree intact but visually removes boot screen.
226
272
  els.forEach(el => {
@@ -234,12 +280,12 @@ export function initAnimations(throttlePerFrame, fps) {
234
280
  if (!bootEls.length) {
235
281
  // no .boot-up -> completely skip boot
236
282
  } else if (bootEls.length > 1) {
237
- 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)
238
284
  hideBootEls(bootEls)
239
285
  hasBooted = true
240
286
  // fall through to normal initListeners without pausing DOM
241
287
  } else if (hasBooted && !bootTimeout) {
242
- // 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
243
289
  console.log(`[initAnimations] skipping boot on path change (already booted)`, bootEls)
244
290
  hideBootEls(bootEls)
245
291
  hasBooted = true
@@ -265,7 +311,7 @@ export function initAnimations(throttlePerFrame, fps) {
265
311
  // ensure boot els are visible even if nested inside hidden ancestors
266
312
  bootEls.forEach(el => { el.style.visibility = 'visible' })
267
313
 
268
- // 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
269
315
  bootCleanup = initListeners(bootEl, effThrottle)
270
316
 
271
317
  bootTimeout = setTimeout(() => {
@@ -300,7 +346,7 @@ export function initAnimations(throttlePerFrame, fps) {
300
346
  }
301
347
 
302
348
  if (!cfg || !from) {
303
- 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`)
304
350
  finish()
305
351
  return
306
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,21 +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
+
30
+ ## [1.0.0-beta.23] - 2026-09-09
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`.
32
+
5
33
  ## [1.0.0-beta.22.2] - 2026-09-06
6
- - 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.
7
35
 
8
36
  ## [1.0.0-beta.22.1] - 2026-09-06
9
- - 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.
10
38
  - No API change from `beta.22`.
11
39
 
12
40
  ## [1.0.0-beta.22] - 2026-09-06
13
- - 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`).
14
- - 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.
15
- - Fixed runtime `customAnims` re-normalization — `Listeners.js:190` now calls `normalize(customAnims)` inside `initListeners()` so `customAnims.push()` before next `init` is picked up.
16
- - 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`.
17
- - 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.
18
- - 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.
19
- - 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`).
20
48
 
21
49
  ## [1.0.0-beta.21] - 2026-9-3
22
50
  - Added a `gclassOpts()` function that controls the animation fps and observer throttling
@@ -48,22 +76,22 @@ All notable changes to `gclass-anims` will be documented in this file.
48
76
 
49
77
  ## [1.0.0-beta.12] - 2026-08-26
50
78
 
51
- - 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`).
52
80
 
53
81
  ## [1.0.0-beta.11] - 2026-08-26
54
82
 
55
- - 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.
56
84
 
57
85
  ## [1.0.0-beta.10] - 2026-08-26
58
86
 
59
- - 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`).
60
- - Added `.draw` - stroke-draw reveal for SVG paths using DrawSVGPlugin (`drawSVG: 0% → 100%`).
61
- - Added `.draw-split` - draws multi-segment SVG paths sequentially at constant pen speed (splits paths with multiple `M` commands into individual strokes).
62
- - Added `.scramble` - text resolves from empty through scrambled characters into real content (ScrambleTextPlugin). Supports `.reveal-delay-N`, `.chars-[...]`, `.amount-N`, `.scramble-rtl`.
63
- - Added `.scramble-all` - variant of scramble with no empty start; the finished string flips to garbage as a whole then sweeps back.
64
- - Added `.scroll-frame` - use a scrollable container as the ScrollTrigger scroller for nested `.scroll` / `.scroll-progress` elements (innermost `.scroll-frame` ancestor wins).
65
- - 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.
66
94
 
67
- ## [1.0.0-beta.9] - Previous release
95
+ ## [1.0.0-beta.9]-Previous release
68
96
 
69
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,