├── .env.example
├── .github
└── FUNDING.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CNAME
├── LICENSE
├── README.md
├── assets
├── all.gif
├── brand
│ ├── usal_icon.svg
│ ├── usal_icon_144.gif
│ ├── usal_icon_144.png
│ ├── usal_icon_144.webp
│ ├── usal_icon_16.gif
│ ├── usal_icon_16.png
│ ├── usal_icon_16.webp
│ ├── usal_icon_192.gif
│ ├── usal_icon_192.png
│ ├── usal_icon_192.webp
│ ├── usal_icon_32.gif
│ ├── usal_icon_32.png
│ ├── usal_icon_32.webp
│ ├── usal_icon_48.gif
│ ├── usal_icon_48.png
│ ├── usal_icon_48.webp
│ ├── usal_icon_512.gif
│ ├── usal_icon_512.png
│ ├── usal_icon_512.webp
│ ├── usal_icon_72.gif
│ ├── usal_icon_72.png
│ ├── usal_icon_72.webp
│ ├── usal_icon_96.gif
│ ├── usal_icon_96.png
│ ├── usal_icon_96.webp
│ ├── usal_logo.svg
│ └── usal_logo_dark.svg
├── count.gif
├── logo.png
├── split.gif
└── text.gif
├── dev
├── debug.css
├── debug.html
└── debug.js
├── docs
├── API.md
├── CNAME
├── favicon.png
├── index.html
├── llms.txt
├── playground.css
├── playground.html
├── playground.js
└── style.css
├── eslint.config.js
├── package-lock.json
├── package.json
├── scripts
├── build.js
├── colorize.js
├── lint-texts.js
├── postbuild.js
├── publish.js
└── release-gh.js
├── src
├── integrations
│ ├── angular.ts
│ ├── lit.ts
│ ├── react.tsx
│ ├── solid.ts
│ ├── svelte.ts
│ └── vue.ts
├── plugins
│ ├── nuxt-plugin.js
│ └── nuxt.js
├── types
│ ├── lit.d.ts
│ ├── react.d.ts
│ ├── solid.d.ts
│ ├── svelte.d.ts
│ └── vue.d.ts
├── usal.d.ts
└── usal.js
├── test
├── angular
│ ├── .gitignore
│ ├── angular.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── app
│ │ │ └── app.ts
│ │ ├── index.html
│ │ ├── main.server.ts
│ │ ├── main.ts
│ │ └── server.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ └── tsconfig.spec.json
├── lit
│ ├── .gitignore
│ ├── .prettierrc.json
│ ├── LICENSE
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── res
│ │ └── drawable
│ │ │ ├── favicon.svg
│ │ │ ├── logo-192x192.png
│ │ │ ├── logo-48x48.png
│ │ │ ├── logo-512x512.png
│ │ │ └── og
│ │ │ └── default.png
│ ├── src
│ │ ├── base
│ │ │ ├── app-404.ts
│ │ │ ├── app-localstorage.ts
│ │ │ ├── app-router.ts
│ │ │ └── styles
│ │ │ │ ├── default-theme.ts
│ │ │ │ └── lit-shared-styles.ts
│ │ ├── home
│ │ │ └── activities
│ │ │ │ └── home-app.ts
│ │ ├── lit-app.ts
│ │ ├── login
│ │ │ └── activities
│ │ │ │ └── app-login.ts
│ │ └── xconfig
│ │ │ ├── strings
│ │ │ ├── index.ts
│ │ │ └── lang
│ │ │ │ └── es.ts
│ │ │ └── typings.d.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── nuxt
│ ├── .gitignore
│ ├── app
│ │ └── app.vue
│ ├── nuxt.config.ts
│ ├── package-lock.json
│ ├── package.json
│ └── tsconfig.json
├── react
│ ├── .gitignore
│ ├── app
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── next.config.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.mjs
│ └── tsconfig.json
├── solid
│ ├── .gitignore
│ ├── app.config.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── app.css
│ │ ├── app.tsx
│ │ ├── components
│ │ │ ├── Counter.css
│ │ │ └── Counter.tsx
│ │ ├── entry-client.tsx
│ │ ├── entry-server.tsx
│ │ ├── global.d.ts
│ │ └── routes
│ │ │ ├── [...404].tsx
│ │ │ ├── about.tsx
│ │ │ └── index.tsx
│ └── tsconfig.json
├── svelte
│ ├── .gitignore
│ ├── .npmrc
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── app.d.ts
│ │ ├── app.html
│ │ ├── lib
│ │ │ ├── assets
│ │ │ │ └── favicon.svg
│ │ │ └── index.ts
│ │ └── routes
│ │ │ ├── +layout.svelte
│ │ │ └── +page.svelte
│ ├── static
│ │ └── robots.txt
│ ├── svelte.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
└── vue
│ ├── .gitignore
│ ├── index.html
│ ├── jsconfig.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── App.vue
│ ├── assets
│ │ ├── base.css
│ │ ├── logo.svg
│ │ └── main.css
│ ├── components
│ │ ├── HelloWorld.vue
│ │ ├── TheWelcome.vue
│ │ ├── WelcomeItem.vue
│ │ └── icons
│ │ │ ├── IconCommunity.vue
│ │ │ ├── IconDocumentation.vue
│ │ │ ├── IconEcosystem.vue
│ │ │ ├── IconSupport.vue
│ │ │ └── IconTooling.vue
│ └── main.js
│ └── vite.config.js
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=example.com
2 | NEXT_PUBLIC_ALLOWED_HOST=example.com
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: italoalmeida0
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | pnpm-debug.log*
7 | lerna-debug.log*
8 |
9 | # Build outputs
10 | dist/
11 | packages/
12 | *.tgz
13 |
14 | # Environment variables
15 | .env
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | # IDE/Editor files
22 | .vscode/
23 | .idea/
24 | *.swp
25 | *.swo
26 | *~
27 | *.sublime-project
28 | *.sublime-workspace
29 |
30 | # OS generated files
31 | .DS_Store
32 | .DS_Store?
33 | ._*
34 | .Spotlight-V100
35 | .Trashes
36 | ehthumbs.db
37 | Thumbs.db
38 | desktop.ini
39 |
40 | # Logs
41 | logs/
42 | *.log
43 | lerna-debug.log*
44 | .pnpm-debug.log*
45 |
46 | # Runtime data
47 | pids
48 | *.pid
49 | *.seed
50 | *.pid.lock
51 |
52 | # Testing
53 | coverage/
54 | *.lcov
55 | .nyc_output
56 |
57 | # Caches
58 | .npm
59 | .eslintcache
60 | .stylelintcache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 | .cache
66 | .parcel-cache
67 | .turbo
68 |
69 | # Optional REPL history
70 | .node_repl_history
71 |
72 | # Yarn
73 | .yarn-integrity
74 | .yarn/*
75 | !.yarn/patches
76 | !.yarn/plugins
77 | !.yarn/releases
78 | !.yarn/sdks
79 | !.yarn/versions
80 |
81 | # pnpm
82 | .pnp.*
83 | .pnpm-store/
84 |
85 | # Build tools
86 | .grunt
87 | .sass-cache
88 |
89 | # Temporary folders
90 | tmp/
91 | temp/
92 | .tmp/
93 |
94 | # TypeScript
95 | *.tsbuildinfo
96 | .tsc-cache/
97 |
98 | # Next.js
99 | .next/
100 | out/
101 |
102 | # Nuxt.js
103 | .nuxt/
104 |
105 | # Gatsby
106 | .cache/
107 | public/
108 |
109 | # Storybook
110 | .out
111 | .storybook-out
112 | storybook-static/
113 |
114 | # Rollup
115 | .rollup.cache
116 |
117 | # Vite
118 | vite.config.js.timestamp-*
119 | vite.config.ts.timestamp-*
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package-lock.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "es5",
4 | "singleQuote": true,
5 | "printWidth": 100,
6 | "tabWidth": 2,
7 | "useTabs": false,
8 | "bracketSpacing": true,
9 | "bracketSameLine": false,
10 | "arrowParens": "always",
11 | "endOfLine": "lf"
12 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to USAL.js will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [1.3.1] - 2025-10-06
9 |
10 | ### Fixed
11 |
12 | - **Blazor component reuse compatibility**: Fixed edge case where Blazor's DOM element reuse caused animations to trigger on elements without `data-usal` attributes
13 | - USAL now ignores elements with empty or whitespace-only `data-usal` values
14 | - Prevents animations from triggering when Blazor strips attributes during navigation
15 | - Special thanks to [@mdmontesinos](https://github.com/mdmontesinos) for the detailed investigation and testing [TypeError: Cannot read properties of null (reading 'replaceChild') #5](https://github.com/usaljs/usal/issues/5)
16 |
17 | - **Tab visibility desynchronization**: Fixed stagger timing issues when browser tabs become inactive
18 | - Implemented virtual time system capped at 16.67ms per frame
19 | - Stagger delays now remain synchronized after tab switches
20 | - Eliminates animation "bunching" when returning to inactive tabs
21 | - Applies to both split animations and count animations
22 |
23 | - **Console error handling**: Improved error handling for `replaceChild` operations during DOM manipulation
24 | - Elements causing errors are now properly ignored and cleaned up
25 | - No visual impact on animations
26 | - Reduces console spam in edge cases
27 |
28 | - **Split animation nesting**: Fixed excessive DOM nesting in split letter/word animations
29 | - Proper instance detection prevents duplicate wrapper generation
30 | - Cleaner DOM structure with minimal nesting
31 |
32 | ## [1.3.0] - 2025-09-20
33 |
34 | ### Added
35 |
36 | - **Brand identity refresh**: New logos and icon set with dark mode support
37 | - SVG logos with automatic dark/light theme switching
38 | - Complete icon set in multiple sizes (16px to 512px) and formats (PNG, WebP, GIF)
39 | - Updated favicon and social media assets
40 |
41 | - **Text animation effects via timeline**: Shimmer and fluid effects now use timeline syntax
42 | - `text-shimmer`: Converted to `line-[o+50g+100|50o+100g+130|o+50g+100]`
43 | - `text-fluid`: Converted to `line-[w+100|50w+900|w+100]`
44 | - Effects can now be customized and combined with other timeline properties
45 |
46 | - **Timeline property extensions**: New animation properties for advanced effects
47 | - `g±value`: Glow/brightness control (0-100+)
48 | - `w±value`: Font weight morphing (100-900)
49 |
50 | - **Comprehensive test suites**: Added integration tests for all major frameworks
51 | - Angular, React/Next.js, Vue/Nuxt, Svelte/SvelteKit, Solid, Lit
52 | - Framework-specific examples and edge case testing
53 |
54 | ### Fixed
55 |
56 | - **SSR compatibility**: Complete overhaul for server-side rendering safety
57 | - HTML structure preservation during split/count animations
58 | - No element cloning or reconstruction
59 | - Only text nodes modified for text/count animations
60 | - Eliminated hydration mismatches in SSR frameworks
61 |
62 | - **Animation synchronization**: Fixed 5-95% progress bug causing visual artifacts
63 | - Animations now use full 0-100% range with proper edge handling
64 | - Eliminated "snap" effect at animation boundaries
65 | - Improved timing precision and synchronization
66 |
67 | - **Angular integration**: Resolved directive compatibility issues
68 | - Migrated to standalone directive architecture
69 | - Fixed attribute binding with proper `usal="value"` syntax
70 | - Added platform browser checks for SSR safety
71 |
72 | - **Default configuration**: Improved configuration inheritance system
73 | - `config.defaults` now properly cascades through all animation types
74 | - Consistent application of default values across split animations
75 | - Better respect for user-defined defaults
76 |
77 | ### Changed
78 |
79 | - **Documentation improvements**: Reorganized for better discoverability
80 | - Framework usage examples moved directly below installation
81 | - Added Vue.js plugin setup alongside Nuxt configuration
82 | - Updated API documentation with new timeline properties
83 |
84 | - **Animation engine robustness**: Enhanced safety and error handling
85 | - Added processing locks to prevent race conditions
86 | - Improved cleanup with proper promise chains
87 | - Better memory management with element state tracking
88 | - Enhanced edge case handling for complex DOM mutations
89 |
90 | ### Performance
91 |
92 | - **DOM manipulation optimization**: Reduced layout thrashing
93 | - Text node replacement instead of innerHTML manipulation
94 | - RequestAnimationFrame batching for DOM updates
95 | - Eliminated unnecessary style recalculations
96 |
97 | ## [1.2.3] - 2025-09-16
98 |
99 | ### Added
100 |
101 | - **Slide animation**: New `slide` animation type for pure movement without opacity changes
102 | - Supports all directional variants (slide-u, slide-d, slide-l, slide-r, etc.)
103 | - Maintains original element opacity throughout animation
104 | - Useful for elements that need to stay fully visible during entrance
105 |
106 | ### Fixed
107 |
108 | - **Split text HTML preservation**: Split animations now correctly preserve HTML structure
109 | - Bold, italic, and other inline elements are maintained during split
110 | - Nested HTML elements remain properly formatted
111 | - Fixed issue where `textContent` was destroying HTML tags
112 | - **Count animation HTML preservation**: Count animations now work with formatted text
113 | - Preserves surrounding HTML elements when replacing numbers
114 | - Works correctly with nested HTML structures
115 |
116 | - **Animation type persistence**: Fixed parser bug that reset animation types
117 | - Animation configuration no longer lost when processing subsequent tokens
118 | - Fixed issue where all animations were defaulting to fade
119 |
120 | - **Split animation tuning**: Fixed issue where split animations lost tuning values
121 | - Tuning parameters (e.g., fade-u-200) now correctly preserved with split text
122 | - Empty configuration arrays no longer override valid tuning values
123 |
124 | - **Letter animation display**: Fixed inline-block application for split letter animations
125 | - Letters now animate correctly with proper display properties
126 | - Fixed "snap" effect when animations complete
127 | - Proper cleanup maintains inline-block for split elements
128 |
129 | - **Build script compatibility**: Fixed Node.js version compatibility in build script
130 | - Added fallback for `file.path` in recursive directory reading
131 | - Works across Node.js versions 18-24
132 |
133 | ### Changed
134 |
135 | - **Internal refactoring**: Improved code organization
136 | - Extracted `genEmptyConfig()` function for configuration generation
137 | - Renamed `splitByNotItem` to `isSplitText` for clarity
138 | - Better separation of concerns in split and count setup functions
139 |
140 | ### Performance
141 |
142 | - **Animation cleanup**: Improved cleanup of cancelled animations
143 | - Better garbage collection hints with effect/timeline nullification
144 | - More efficient memory management for long-running applications
145 |
146 | ## [1.2.2] - 2025-09-10
147 |
148 | ### Added
149 |
150 | - **Advanced loop types**: New `loop-mirror` and `loop-jump` modifiers for different loop behaviors
151 | - `loop-mirror`: Back and forth animation (default behavior)
152 | - `loop-jump`: Restart animation from beginning
153 | - **Enhanced split delay stagger patterns**: New stagger types for split animations
154 | - `split-delay-{value}-linear`: Linear distance-based stagger
155 | - `split-delay-{value}-center`: Center-outward stagger on X/Y axes
156 | - `split-delay-{value}-edges`: Edges-inward stagger on X/Y axes
157 | - `split-delay-{value}-random`: Random stagger pattern
158 | - **Improved blur precision**: Support for decimal blur values (e.g., `blur-0.5`, `blur-1.5`)
159 | - **Enhanced easing options**: Added `ease-in-out`, `step-start`, and `step-end` easing functions
160 |
161 | ### Changed
162 |
163 | - **Loop configuration**: Default loop type can now be configured via `config.defaults.loop` (default: 'mirror')
164 | - **Playground improvements**: Enhanced controls and preset examples including new stagger effects
165 | - **Animation engine**: Refactored animation controller with better state management and performance
166 | - **Split animation syntax**: Improved parsing for split animations with tuning parameters
167 |
168 | ## [1.2.1] - 2025-09-09
169 |
170 | ### Fixed
171 |
172 | - **Animation tuning**: Fixed parsing of tuning values when no direction is specified (e.g., `fade-50`, `flip-90` now work correctly)
173 |
174 | ## [1.2.0] - 2025-09-09
175 |
176 | ### Added
177 |
178 | - **Loop animations**: New `loop` modifier for continuous animation cycles
179 | - **Custom timeline animations**: Advanced `line-[{timeline}]` syntax for precise keyframe control
180 | - Support for opacity, scale, translate, rotate, blur, and perspective properties
181 | - Multi-keyframe animations with percentage-based timing
182 | - 3D transformations with proper transform order handling
183 | - **Animation tuning**: Numeric parameters for fine-tuning standard animations
184 | - Fade movement distance control (e.g., `fade-40` for 40% movement)
185 | - Zoom intensity and movement customization (e.g., `zoomin-40-60`)
186 | - Flip angle and perspective adjustment (e.g., `flip-120-50`)
187 | - **Enhanced blur effects**: Custom blur intensity with `blur-{value}` syntax
188 | - **Forwards modifier**: `forwards` option to maintain final animation state
189 | - **Comprehensive debug panel**: Extended testing suite for new features
190 |
191 | ### Changed
192 |
193 | - **Bundle size**: Updated from ~5KB to ~8KB gzipped due to new advanced features
194 | - **API documentation**: Moved detailed API docs to separate file and wiki
195 | - **Framework icons**: Added emoji icons to framework setup sections
196 | - **Performance comparison**: Updated competitor bundle sizes and feature matrix
197 |
198 | ### Enhanced
199 |
200 | - **Configuration system**: Extended config array to support new animation types
201 | - **Split animations**: Improved handling with new animation tuning system
202 | - **Transform composition**: Better transform order handling for complex animations
203 | - **Style management**: Enhanced keyframe generation for custom timelines
204 |
205 | ### Fixed
206 |
207 | - **Element cleanup**: Improved cleanup process for disconnected elements
208 | - **Animation state**: Better handling of loop and forwards states
209 | - **Split text**: Enhanced inline-block display for split animations
210 |
211 | ## [1.1.1] - 2025-09-08
212 |
213 | ### Changed
214 |
215 | - Complete API overhaul with promise-based chain system using Rust-like enum states
216 | - Significantly improved edge case handling for extreme usage scenarios
217 | - Better orchestrated state machine for animation lifecycle
218 | - Enhanced public API usability and consistency
219 | - Improved error handling and recovery mechanisms
220 |
221 | ### Added
222 |
223 | - Comprehensive debug panel in source code for development testing (debug.html, debug.css, debug.js)
224 |
225 | ### Fixed
226 |
227 | - Text animations losing characteristics in flex containers without wrapper
228 | - Smooth scroll issue on mobile when switching tabs
229 |
230 | ## [1.1.0] - 2025-09-07
231 |
232 | ### Changed
233 |
234 | - **BREAKING**: Complete migration to Web Animations API (WAAPI) for all animations
235 | - Only count animations remain using RAF for precise number formatting
236 | - Zero direct DOM manipulation - no inline styles or attributes
237 | - Eliminated SSR hydration mismatches
238 | - No longer manipulates HTML node attributes directly
239 |
240 | ### Added
241 |
242 | - Perfect compatibility with React, Vue, Svelte, Solid, and other SSR frameworks
243 | - Better browser optimization and hardware acceleration
244 |
245 | ### Performance
246 |
247 | - Significantly improved performance through WAAPI
248 | - Reduced JavaScript overhead
249 | - Better memory management
250 |
251 | ## [1.0.0] - 2025-09-03
252 |
253 | ### Added
254 |
255 | - Initial release with 40+ scroll-triggered animations
256 | - Intersection Observer based triggers
257 | - Zero dependencies, ~5KB gzipped
258 | - Full Shadow DOM support
259 | - Framework packages for React, Vue, Svelte, Solid, Lit, Angular
260 | - Automatic initialization
261 | - Split text animations (word/letter)
262 | - Count animations with smart number formatting
263 | - Custom easing support
264 | - Blur effects
265 | - Threshold controls
266 | - Duration and delay modifiers
267 |
268 | [1.3.1]: https://github.com/usaljs/usal/compare/v1.3.0...v1.3.1
269 | [1.3.0]: https://github.com/usaljs/usal/compare/v1.2.3...v1.3.0
270 | [1.2.3]: https://github.com/usaljs/usal/compare/v1.2.2...v1.2.3
271 | [1.2.2]: https://github.com/usaljs/usal/compare/v1.2.1...v1.2.2
272 | [1.2.1]: https://github.com/usaljs/usal/compare/v1.2.0...v1.2.1
273 | [1.2.0]: https://github.com/usaljs/usal/compare/v1.1.1...v1.2.0
274 | [1.1.1]: https://github.com/usaljs/usal/compare/v1.1.0...v1.1.1
275 | [1.1.0]: https://github.com/usaljs/usal/compare/v1.0.0...v1.1.0
276 | [1.0.0]: https://github.com/usaljs/usal/releases/tag/v1.0.0
277 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | usal.dev
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Italo Almeida
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | **Ultimate Scroll Animation Library - Lightweight, powerful, wonderfully simple ✨**
13 |
14 | **Works with React, Solid, Svelte, Vue, Lit, Angular, Vanilla JS and more**
15 |
16 | **[> usal.dev/](https://usal.dev/)**
17 |
18 | [](https://npmjs.com/package/usal) [](https://discord.usal.dev/)
19 |
20 | [](https://www.cloudflare.com/) [](https://jsdelivr.com) [](https://github.com/sponsors/italoalmeida0)
21 |
22 |
23 |
24 | ## ✨ Features
25 |
26 | - 🎯 **40+ animations** (fade, zoom, flip with all directions)
27 | - 📝 **Text animations** (split by word/letter)
28 | - 🔢 **Number counters**
29 | - 🎨 **Text effects** (shimmer, fluid)
30 | - 📦 **Only 8KB Gzipped**
31 | - 🚀 **Zero dependencies**
32 | - ♾ **60fps performance**
33 | - 🪤 **Web components supported**
34 | - 🔧 **Framework agnostic**
35 | - ⚡ **CDN powered** by jsDelivr & Cloudflare
36 |
37 | ## 📦 Installation
38 |
39 | ### CDN (Quickstart)
40 |
41 | ```html
42 |
43 | ```
44 |
45 | ### NPM
46 |
47 | ```bash
48 | npm install usal
49 |
50 | # Framework-specific packages
51 | npm install @usal/react # For React/Next.js
52 | npm install @usal/solid # For Solid/SolidStart
53 | npm install @usal/svelte # For Svelte/SvelteKit
54 | npm install @usal/vue # For Vue/Nuxt
55 | npm install @usal/lit # For Lit
56 | npm install @usal/angular # For Angular
57 | ```
58 |
59 | ## 🚀 Framework Setup
60 |
61 | ### ⬛ React (Next.js)
62 |
63 | ```jsx
64 | import { USALProvider } from '@usal/react';
65 | {children} ;
66 | ```
67 |
68 | ### 🟦 Solid (SolidStart)
69 |
70 | ```jsx
71 | import { USALProvider } from '@usal/solid';
72 | {props.children} ;
73 | ```
74 |
75 | ### 🟧 Svelte (SvelteKit)
76 |
77 | ```js
78 | import { usal } from '@usal/svelte';
79 | // USAL auto-initializes globally
80 | ```
81 |
82 | ### 🟩 Vue (Nuxt)
83 |
84 | ```js
85 | import { USALPlugin } from '@usal/vue';
86 | createApp(App).use(USALPlugin).mount('#app');
87 | //for Nuxt
88 | export default defineNuxtConfig({
89 | modules: ['@usal/vue/nuxt']
90 | //...
91 | ```
92 |
93 | ### 🟪 Lit
94 |
95 | ```js
96 | import { usal } from '@usal/lit';
97 | // USAL auto-initializes globally
98 | ```
99 |
100 | ### 🟥 Angular
101 |
102 | ```js
103 | import { USALModule } from '@usal/angular';
104 | @Component({imports: [USALModule]})
105 | export class AppComponent
106 | ```
107 |
108 | ## 📐 Basic Usage
109 |
110 | ```html
111 |
112 | Fade from bottom
113 |
114 |
115 | Zoom in
116 |
117 |
118 | Flip from right
119 | ```
120 |
121 | ## [📖 Complete API Documentation](https://github.com/usaljs/usal/wiki/API-Documentation) or https://usal.dev/#api
122 |
123 | ## 🎲 Demos
124 |
125 | 
126 |
127 | 
128 |
129 | 
130 |
131 | 
132 |
133 | ## 📊 Packages Overview
134 |
135 | | Package | Version |
136 | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
137 | | `usal` |  |
138 | | `@usal/react` |  |
139 | | `@usal/solid` |  |
140 | | `@usal/svelte` |  |
141 | | `@usal/vue` |  |
142 | | `@usal/lit` |  |
143 | | `@usal/angular` |  |
144 |
145 | ## 📈 JavaScript Animation Frameworks Comparison (2025)
146 |
147 | ### Performance & Size Comparison
148 |
149 | | Framework | Bundle Size (gzip) | React | Vue | Angular | Svelte | Solid | Lit | Vanilla |
150 | | -------------- | ------------------ | --------- | --------- | --------- | --------- | --------- | --------- | ------- |
151 | | **🚀 USAL.js** | **~8KB** | ✅ Native | ✅ Native | ✅ Native | ✅ Native | ✅ Native | ✅ Native | ✅ |
152 | | Motion One | Variable (~small) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
153 | | GSAP | ~28KB | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
154 | | Anime.js v4 | ~27KB | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
155 | | Lottie | ~60KB | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ |
156 | | AOS | ~8KB | ⚠️ | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ✅ |
157 | | SAL.js | ~2.7KB | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
158 |
159 | ### Feature Comparison
160 |
161 | | Framework | Split (Letters/Words/Items) | Counters | Scroll Trigger | Timeline | SVG | Learning |
162 | | ----------- | --------------------------- | --------- | -------------- | ----------- | --------- | ------------- |
163 | | **USAL.js** | ✅ Core | ✅ Core | ✅ Core | ✅ Core | ❌ | **Very Easy** |
164 | | Motion | ❌ | ❌ | ✅ Core | ⚠️ Variants | ✅ Core | Medium |
165 | | GSAP | ⚠️ Plugin | ⚠️ Plugin | ✅ Plugin | ✅ Advanced | ⚠️ Plugin | Complex |
166 | | Anime.js v4 | ✅ Core | ✅ Core | ✅ Core | ✅ Core | ✅ Core | Medium |
167 | | Lottie | ⚠️ via AE | ⚠️ via AE | ❌ | ✅ Core | ✅ Core | Complex |
168 | | AOS | ❌ | ❌ | ✅ Core | ❌ | ❌ | Very Easy |
169 | | SAL.js | ❌ | ❌ | ✅ Core | ❌ | ❌ | Very Easy |
170 |
171 | **Legend:**
172 |
173 | - ✅ Native/Core support
174 | - ⚠️ Plugin/Wrapper required
175 | - ❌ Not supported
176 |
177 | ## 🙏 Acknowledgments
178 |
179 | USAL.js was inspired by:
180 |
181 | - **[AOS.js](https://github.com/michalsnik/aos)** - Pioneering attribute-based animations
182 | - **[SAL.js](https://github.com/mciastek/sal)** - Lightweight performance optimization
183 | - **[Tailwind CSS](https://tailwindcss.com)** - Utility-first naming conventions
184 |
185 | ---
186 |
187 | ## 📄 License
188 |
189 | MIT License © 2025 Italo Almeida ([@italoalmeida0](https://github.com/italoalmeida0))
190 |
--------------------------------------------------------------------------------
/assets/all.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/all.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/assets/brand/usal_icon_144.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_144.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_144.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_144.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_144.webp
--------------------------------------------------------------------------------
/assets/brand/usal_icon_16.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_16.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_16.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_16.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_16.webp
--------------------------------------------------------------------------------
/assets/brand/usal_icon_192.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_192.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_192.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_192.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_192.webp
--------------------------------------------------------------------------------
/assets/brand/usal_icon_32.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_32.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_32.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_32.webp
--------------------------------------------------------------------------------
/assets/brand/usal_icon_48.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_48.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_48.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_48.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_48.webp
--------------------------------------------------------------------------------
/assets/brand/usal_icon_512.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_512.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_512.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_512.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_512.webp
--------------------------------------------------------------------------------
/assets/brand/usal_icon_72.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_72.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_72.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_72.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_72.webp
--------------------------------------------------------------------------------
/assets/brand/usal_icon_96.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_96.gif
--------------------------------------------------------------------------------
/assets/brand/usal_icon_96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_96.png
--------------------------------------------------------------------------------
/assets/brand/usal_icon_96.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/brand/usal_icon_96.webp
--------------------------------------------------------------------------------
/assets/brand/usal_logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/brand/usal_logo_dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/count.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/count.gif
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/logo.png
--------------------------------------------------------------------------------
/assets/split.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/split.gif
--------------------------------------------------------------------------------
/assets/text.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/assets/text.gif
--------------------------------------------------------------------------------
/dev/debug.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
9 | background: #0a0a0a;
10 | color: #888;
11 | padding: 20px;
12 | padding-right: 440px;
13 | line-height: 1.6;
14 | }
15 |
16 | h1,
17 | h2 {
18 | color: #fff;
19 | margin-bottom: 20px;
20 | }
21 |
22 | h2 {
23 | color: #666;
24 | font-size: 1.2rem;
25 | border-bottom: 1px solid #222;
26 | padding-bottom: 10px;
27 | }
28 |
29 | /* Control Panel */
30 | .controls {
31 | position: fixed;
32 | top: 0;
33 | right: 0;
34 | background: #0a0a0a;
35 | border-left: 1px solid #333;
36 | padding: 20px;
37 | width: 420px;
38 | height: 100vh;
39 | overflow-y: auto;
40 | box-shadow: -5px 0 20px rgba(0, 0, 0, 0.5);
41 | z-index: 1000;
42 | }
43 |
44 | .controls h3 {
45 | color: #fff;
46 | margin: 15px 0 10px;
47 | font-size: 0.9rem;
48 | border-top: 1px solid #333;
49 | padding-top: 10px;
50 | }
51 |
52 | .controls h3:first-child {
53 | border-top: none;
54 | padding-top: 0;
55 | margin-top: 0;
56 | }
57 |
58 | .controls button {
59 | display: inline-block;
60 | margin: 3px;
61 | padding: 6px 10px;
62 | background: #1a1a1a;
63 | color: #888;
64 | border: 1px solid #333;
65 | cursor: pointer;
66 | font-family: monospace;
67 | font-size: 11px;
68 | transition: all 0.2s;
69 | }
70 |
71 | .controls button:hover {
72 | background: #222;
73 | color: #fff;
74 | border-color: #444;
75 | }
76 |
77 | .controls button:active {
78 | transform: scale(0.98);
79 | }
80 |
81 | .controls button.active {
82 | background: #333;
83 | color: #4ade80;
84 | border-color: #4ade80;
85 | }
86 |
87 | /* Sections */
88 | .section {
89 | margin: 40px 0;
90 | padding: 20px;
91 | border: 1px solid #222;
92 | background: #0f0f0f;
93 | }
94 |
95 | .grid {
96 | display: grid;
97 | grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
98 | gap: 10px;
99 | margin: 20px 0;
100 | }
101 |
102 | /* Test Boxes */
103 | .test-box {
104 | background: #111;
105 | border: 1px solid #222;
106 | padding: 15px;
107 | text-align: center;
108 | min-height: 80px;
109 | display: flex;
110 | flex-direction: column;
111 | justify-content: center;
112 | align-items: center;
113 | position: relative;
114 | color: #666;
115 | font-size: 12px;
116 | transition: all 0.3s;
117 | }
118 |
119 | .test-box:hover {
120 | border-color: #444;
121 | }
122 |
123 | .test-box::before {
124 | content: attr(data-usal);
125 | position: absolute;
126 | top: 2px;
127 | left: 2px;
128 | right: 2px;
129 | font-size: 8px;
130 | color: #333;
131 | text-align: left;
132 | white-space: nowrap;
133 | overflow: hidden;
134 | text-overflow: ellipsis;
135 | }
136 |
137 | .test-box.has-animation {
138 | border-color: #4ade80;
139 | box-shadow: 0 0 10px rgba(74, 222, 128, 0.2);
140 | }
141 |
142 | .test-box.has-fragment {
143 | background: #1a1a0a;
144 | }
145 |
146 | /* Monitor displays */
147 | .monitor {
148 | background: #0a0a0a;
149 | border: 1px solid #222;
150 | padding: 8px;
151 | margin: 10px 0;
152 | font-size: 10px;
153 | font-family: monospace;
154 | max-height: 200px;
155 | overflow-y: auto;
156 | }
157 |
158 | .monitor-item {
159 | padding: 3px 5px;
160 | margin: 2px 0;
161 | border-left: 2px solid #333;
162 | padding-left: 8px;
163 | }
164 |
165 | .monitor-item.active {
166 | border-left-color: #4ade80;
167 | color: #4ade80;
168 | }
169 |
170 | .monitor-item.pending {
171 | border-left-color: #fbbf24;
172 | color: #fbbf24;
173 | }
174 |
175 | .monitor-item.idle {
176 | border-left-color: #666;
177 | color: #666;
178 | }
179 |
180 | /* Status Display */
181 | #status {
182 | position: fixed;
183 | bottom: 20px;
184 | right: 440px;
185 | background: #111;
186 | border: 1px solid #333;
187 | padding: 10px;
188 | font-size: 11px;
189 | color: #666;
190 | font-family: monospace;
191 | z-index: 100;
192 | }
193 |
194 | .status-item {
195 | display: flex;
196 | justify-content: space-between;
197 | margin: 2px 0;
198 | min-width: 150px;
199 | }
200 |
201 | .status-value {
202 | color: #fff;
203 | margin-left: 10px;
204 | }
205 |
206 | .status-value.active {
207 | color: #4ade80;
208 | }
209 |
210 | .status-value.inactive {
211 | color: #ef4444;
212 | }
213 |
214 | /* Shadow DOM Styles */
215 | .shadow-host {
216 | background: #0a0a0a;
217 | border: 2px dashed #333;
218 | padding: 20px;
219 | margin: 20px 0;
220 | position: relative;
221 | }
222 |
223 | .shadow-host::before {
224 | content: 'Shadow DOM';
225 | position: absolute;
226 | top: -10px;
227 | left: 10px;
228 | background: #0a0a0a;
229 | padding: 0 5px;
230 | color: #444;
231 | font-size: 10px;
232 | }
233 |
234 | /* Stress Test Grid */
235 | .stress-grid {
236 | display: grid;
237 | grid-template-columns: repeat(20, 1fr);
238 | gap: 2px;
239 | margin: 20px 0;
240 | }
241 |
242 | .stress-item {
243 | width: 100%;
244 | aspect-ratio: 1;
245 | background: #111;
246 | border: 1px solid #222;
247 | transition: border-color 0.3s;
248 | }
249 |
250 | .stress-item:hover {
251 | border-color: #444;
252 | }
253 |
254 | /* Memory Info */
255 | .memory-info {
256 | background: #0a0a0a;
257 | border: 1px solid #222;
258 | padding: 8px;
259 | margin: 10px 0;
260 | font-size: 10px;
261 | display: grid;
262 | grid-template-columns: 1fr 1fr;
263 | gap: 10px;
264 | }
265 |
266 | .memory-stat {
267 | display: flex;
268 | justify-content: space-between;
269 | }
270 |
271 | .memory-value {
272 | color: #fbbf24;
273 | }
274 |
275 | /* Config Display */
276 | .config-display {
277 | background: #0a0a0a;
278 | border: 1px solid #222;
279 | padding: 8px;
280 | margin: 10px 0;
281 | font-size: 10px;
282 | white-space: pre;
283 | overflow-x: auto;
284 | color: #666;
285 | font-family: monospace;
286 | }
287 |
288 | /* Form Elements */
289 | input,
290 | select {
291 | background: #1a1a1a;
292 | color: #888;
293 | border: 1px solid #333;
294 | padding: 4px 8px;
295 | margin: 3px;
296 | font-family: monospace;
297 | font-size: 11px;
298 | transition: all 0.2s;
299 | }
300 |
301 | input:focus,
302 | select:focus {
303 | outline: none;
304 | border-color: #444;
305 | color: #fff;
306 | background: #222;
307 | }
308 |
309 | input[type='number'] {
310 | width: 80px;
311 | }
312 |
313 | /* Test Runner */
314 | .test-runner {
315 | background: #0f0f0f;
316 | border: 1px solid #222;
317 | padding: 10px;
318 | margin: 10px 0;
319 | max-height: 300px;
320 | overflow-y: auto;
321 | }
322 |
323 | .test-result {
324 | padding: 5px;
325 | margin: 3px 0;
326 | font-size: 11px;
327 | border-left: 2px solid transparent;
328 | padding-left: 8px;
329 | }
330 |
331 | .test-pass {
332 | color: #4ade80;
333 | border-left-color: #4ade80;
334 | }
335 |
336 | .test-fail {
337 | color: #ef4444;
338 | border-left-color: #ef4444;
339 | }
340 |
341 | .test-warn {
342 | color: #fbbf24;
343 | border-left-color: #fbbf24;
344 | }
345 |
346 | /* Performance Metrics */
347 | .perf-display {
348 | display: grid;
349 | grid-template-columns: repeat(3, 1fr);
350 | gap: 10px;
351 | margin: 10px 0;
352 | }
353 |
354 | .perf-metric {
355 | background: #0a0a0a;
356 | border: 1px solid #222;
357 | padding: 8px;
358 | text-align: center;
359 | transition: all 0.3s;
360 | }
361 |
362 | .perf-metric:hover {
363 | border-color: #333;
364 | }
365 |
366 | .perf-value {
367 | font-size: 18px;
368 | color: #4ade80;
369 | font-weight: bold;
370 | }
371 |
372 | .perf-label {
373 | font-size: 10px;
374 | color: #666;
375 | margin-top: 5px;
376 | text-transform: uppercase;
377 | letter-spacing: 1px;
378 | }
379 |
380 | /* Scrollbar Styling */
381 | ::-webkit-scrollbar {
382 | width: 8px;
383 | height: 8px;
384 | }
385 |
386 | ::-webkit-scrollbar-track {
387 | background: #0a0a0a;
388 | }
389 |
390 | ::-webkit-scrollbar-thumb {
391 | background: #333;
392 | border-radius: 4px;
393 | }
394 |
395 | ::-webkit-scrollbar-thumb:hover {
396 | background: #444;
397 | }
398 |
399 | /* Responsive adjustments */
400 | @media (max-width: 1200px) {
401 | body {
402 | padding-right: 20px;
403 | }
404 |
405 | .controls {
406 | width: 350px;
407 | }
408 |
409 | #status {
410 | right: 360px;
411 | }
412 | }
413 |
414 | @media (max-width: 768px) {
415 | .controls {
416 | position: static;
417 | width: 100%;
418 | height: auto;
419 | border: 1px solid #333;
420 | margin-bottom: 20px;
421 | }
422 |
423 | #status {
424 | right: 20px;
425 | }
426 |
427 | .grid {
428 | grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
429 | }
430 | }
431 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | usal.dev
--------------------------------------------------------------------------------
/docs/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italoalmeida0/usal/ea9b62ba1cf004630eff9b792a426a2bf64f222e/docs/favicon.png
--------------------------------------------------------------------------------
/docs/llms.txt:
--------------------------------------------------------------------------------
1 | # USAL.js
2 |
3 | > USAL.js is a lightweight, powerful JavaScript library that brings together scroll animations, text effects, number counters, and custom timelines into a single 8KB package. Framework-agnostic with native support for React, Solid, Svelte, Vue, Lit, Angular, and Vanilla JS. Uses Tailwind-inspired utility-first syntax for creating 60fps scroll-triggered animations.
4 |
5 | Things to remember when using USAL.js:
6 |
7 | - USAL.js uses data attributes for configuration, inspired by Tailwind CSS utility-first approach - no complex JavaScript initialization required
8 | - The library is framework-agnostic but provides native integrations for all major frameworks, not just compatibility wrappers
9 | - Split animations (word/letter/item) and number counters are built-in, unlike most animation libraries that require plugins
10 | - Custom timeline animations provide advanced control with `line-[{timeline}]` syntax for complex keyframe sequences
11 | - All animations are hardware-accelerated using CSS transforms and run at 60fps with automatic cleanup
12 | - Use `threshold-{value}` for large elements (>100vh height) to ensure proper trigger timing
13 |
14 | ## Docs
15 |
16 | - [Complete API Documentation](https://github.com/usaljs/usal/wiki/API-Documentation): Full reference with examples and advanced usage patterns
17 | - [API Summary](https://github.com/usaljs/usal/wiki/API-Summary): Quick reference of all animations, modifiers, and syntax
18 | - [Interactive Playground](https://playground.usal.dev/): Live code editor to test animations and generate code
19 | - [Main Website](https://usal.dev/): Interactive demos with source code for every animation type
20 |
21 | ## Installation & Setup
22 |
23 | ### CDN (Quickstart)
24 | ```html
25 |
26 | ```
27 |
28 | ### NPM
29 | ```bash
30 | npm install usal # Core library
31 | npm install @usal/react # React/Next.js
32 | npm install @usal/solid # Solid/SolidStart
33 | npm install @usal/svelte # Svelte/SvelteKit
34 | npm install @usal/vue # Vue/Nuxt
35 | npm install @usal/lit # Lit
36 | npm install @usal/angular # Angular
37 | ```
38 |
39 | ## Minimal API Reference
40 |
41 | ### Base Animations
42 | - **Fade:** `fade` | `fade-{direction}` | `fade-{distance}` | `fade-{distance}-{distance}` | `fade-{direction}-{distance}` | `fade-{direction}-{distance}-{distance}`
43 | - **Slide:** `slide` | `slide-{direction}` | `slide-{distance}` | `slide-{distance}-{distance}` | `slide-{direction}-{distance}` | `slide-{direction}-{distance}-{distance}`
44 | - **Zoom In:** `zoomin` | `zoomin-{direction}` | `zoomin-{intensity}` | `zoomin-{distance}-{intensity}` | `zoomin-{distance}-{distance}-{intensity}` | `zoomin-{direction}-{intensity}` | `zoomin-{direction}-{distance}-{intensity}` | `zoomin-{direction}-{distance}-{distance}-{intensity}`
45 | - **Zoom Out:** `zoomout` | `zoomout-{direction}` | `zoomout-{intensity}` | `zoomout-{distance}-{intensity}` | `zoomout-{distance}-{distance}-{intensity}` | `zoomout-{direction}-{intensity}` | `zoomout-{direction}-{distance}-{intensity}` | `zoomout-{direction}-{distance}-{distance}-{intensity}`
46 | - **Flip:** `flip` | `flip-{direction}` | `flip-{angle}` | `flip-{angle}-{perspective}` | `flip-{direction}-{angle}` | `flip-{direction}-{angle}-{perspective}`
47 |
48 |
49 | **Directions:** u, d, l, r, ul, ur, dl, dr
50 |
51 | ### Split Animations
52 | - **Types:** `split-word` | `split-letter` | `split-item`
53 | - **Combined:** `split-{animation}-{direction}` | `split-{animation}-{direction}-{values}`
54 | - **Delays:** `split-delay-{milliseconds}` | `split-delay-{milliseconds}-{stagger}`
55 | - **Stagger types:** linear, center, edges, random
56 |
57 | ### Counters & Effects
58 | - **Count:** `count-[{target}]` (target: numbers, spaces, dots, commas only)
59 | - **Text Effects:** `text-shimmer` | `text-fluid` (requires split-letter)
60 |
61 | ### Custom Timeline
62 | - **Syntax:** `line-[{timeline}]`
63 | - **Properties:** `o±{value}` (opacity 0-100) | `s±{value}` (scale) | `sx/sy/sz±{value}` (scale axis) | `t±{value}` (translateX %) | `tx/ty/tz±{value}` (translate axis %) | `r±{value}` (rotateZ °) | `rx/ry/rz±{value}` (rotate axis °) | `b±{value}` (blur rem) | `g±{value}` (glow/brightness %) | `w±{value}` (font weight 100-900) | `p±{value}` (perspective rem)
64 | - **Keyframes:** `|` (separator) | `|{percentage}` (% position)
65 |
66 | ### Modifiers
67 | - **Timing:** `duration-{ms}` | `delay-{ms}`
68 | - **Easing:** `linear` | `ease` | `ease-in` | `ease-out` | `ease-in-out` | `step-start` | `step-end` | `easing-[{css-function}]`
69 | - **Trigger:** `threshold-{percentage}`
70 | - **Effects:** `blur` | `blur-{rem}` | `once` | `loop` | `loop-{type}` | `forwards`
71 | - **Loop types:** mirror, jump
72 |
73 | ## Framework Usage Examples
74 |
75 | ```html
76 |
77 | Content
78 |
79 |
80 | Content
81 |
82 |
83 | Content
84 |
85 |
86 | Content
87 |
88 |
89 | Content
90 |
91 |
92 | Content
93 |
94 |
95 | Content
96 | ```
97 |
98 | ## JavaScript API
99 |
100 | ```javascript
101 | // Configuration
102 | window.USAL.config({
103 | defaults: {
104 | animation: 'fade',
105 | direction: 'u',
106 | duration: 1000,
107 | delay: 0,
108 | threshold: 10,
109 | splitDelay: 30,
110 | easing: 'ease-out',
111 | blur: false,
112 | loop: 'mirror'
113 | },
114 | observersDelay: 50,
115 | once: false
116 | });
117 |
118 | // Control methods
119 | window.USAL.initialized() // Check if running (auto initialized)
120 | window.USAL.restart() // Restart USAL, use only extreme cases, the system is reactive even in shadowRoot
121 | window.USAL.destroy() // Shut down, use only extreme cases, the system is reactive even in shadowRoot
122 | window.USAL.version // Get version
123 | ```
124 |
125 | ## Performance Comparison (2025)
126 |
127 | | Framework | Bundle Size | Framework Support | Split Animations | Number Counters | Learning Curve |
128 | |-----------|-------------|-------------------|------------------|-----------------|----------------|
129 | | **USAL.js** | **~8KB** | **All major + native** | **✅ Core** | **✅ Core** | **Very Easy** |
130 | | GSAP | ~28KB | All major | ⚠️ Plugin | ⚠️ Plugin | Complex |
131 | | Anime.js v4 | ~27KB | All major | ✅ Core | ✅ Core | Medium |
132 | | Motion One | Variable | Limited | ❌ | ❌ | Medium |
133 | | Lottie | ~60KB | Limited | ⚠️ via AE | ⚠️ via AE | Complex |
134 | | AOS | ~8KB | ⚠️ Limited | ❌ | ❌ | Very Easy |
135 |
136 | ## Key Features
137 |
138 | - **40+ animations** with directional variations (fade, zoom, flip)
139 | - **Text animations** (split by word/letter/item with stagger options)
140 | - **Number counters** with formatting preservation
141 | - **Text effects** (shimmer, fluid morphing)
142 | - **Custom timeline** system for complex keyframe animations
143 | - **Animation tuning** with numeric parameters
144 | - **Framework agnostic** with native integrations
145 | - **8KB gzipped** with zero dependencies
146 | - **60fps performance** with hardware acceleration
147 | - **CDN powered** by jsDelivr & Cloudflare
148 |
149 | ## Use Cases
150 |
151 | - Landing pages and marketing sites with scroll-triggered reveals
152 | - Product showcases and portfolios with staggered animations
153 | - SaaS applications with smooth onboarding flows
154 | - E-commerce sites with product reveal animations
155 | - Blog and content sites with reading experience enhancements
156 | - Mobile-responsive applications with touch-optimized animations
157 |
158 | ## Technical Details
159 |
160 | - Built with modern JavaScript (ES6+) and TypeScript support
161 | - Uses Intersection Observer API for optimal performance
162 | - CSS transform-based animations for hardware acceleration
163 | - Automatic cleanup and memory management
164 | - No jQuery or external dependencies
165 | - Graceful degradation for older browsers
166 | - Works with Web Components and Shadow DOM
167 |
168 | ## Community & Support
169 |
170 | - **GitHub Repository:** https://github.com/usaljs/usal
171 | - **Issues & Bug Reports:** https://github.com/usaljs/usal/issues
172 | - **NPM Package:** https://npmjs.com/package/usal
173 | - **CDN:** https://cdn.usal.dev/latest
174 | - **Creator:** [@italoalmeida0](https://github.com/italoalmeida0)
175 |
176 | ## Inspiration
177 |
178 | USAL.js was inspired by:
179 | - **AOS.js** - Pioneering attribute-based animations
180 | - **SAL.js** - Lightweight performance optimization
181 | - **Tailwind CSS** - Utility-first naming conventions
182 |
183 | The goal was to combine the best aspects of these libraries while adding modern framework support, advanced features like split animations and custom timelines, and maintaining minimal bundle size.
184 |
185 | ## License
186 |
187 | MIT License © 2025 Italo Almeida
188 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import typescriptPlugin from '@typescript-eslint/eslint-plugin';
3 | import typescriptParser from '@typescript-eslint/parser';
4 | import prettierConfig from 'eslint-config-prettier';
5 | import compatPlugin from 'eslint-plugin-compat';
6 | import importPlugin from 'eslint-plugin-import';
7 | import sonarjs from 'eslint-plugin-sonarjs';
8 | import globals from 'globals';
9 |
10 | export default [
11 | js.configs.recommended,
12 | sonarjs.configs.recommended,
13 | prettierConfig,
14 | compatPlugin.configs['flat/recommended'],
15 | {
16 | files: ['**/*.{js,mjs,cjs,ts}'],
17 | languageOptions: {
18 | ecmaVersion: 'latest',
19 | sourceType: 'module',
20 | globals: {
21 | ...globals.browser,
22 | ...globals.node,
23 | },
24 | },
25 | plugins: {
26 | import: importPlugin,
27 | },
28 | rules: {
29 | 'no-console': ['warn', { allow: ['warn', 'error', 'log'] }],
30 | 'no-unused-vars': [
31 | 'warn',
32 | {
33 | argsIgnorePattern: '^_',
34 | varsIgnorePattern: '^_',
35 | },
36 | ],
37 | 'no-debugger': 'warn',
38 | 'prefer-const': 'error',
39 | 'no-var': 'error',
40 | eqeqeq: ['error', 'always', { null: 'ignore' }],
41 | 'arrow-body-style': ['error', 'as-needed'],
42 | 'prefer-arrow-callback': ['error', { allowNamedFunctions: true }],
43 | 'no-duplicate-imports': 'error',
44 | 'no-useless-return': 'error',
45 | 'no-else-return': 'error',
46 |
47 | 'no-constant-condition': 'error',
48 | 'no-unreachable': 'error',
49 | 'no-unneeded-ternary': 'error',
50 | 'no-useless-computed-key': 'error',
51 | 'no-useless-constructor': 'error',
52 | 'no-useless-escape': 'error',
53 | 'no-useless-rename': 'error',
54 |
55 | 'sonarjs/no-identical-conditions': 'error',
56 | 'sonarjs/no-duplicated-branches': 'error',
57 | 'sonarjs/no-redundant-boolean': 'error',
58 | 'sonarjs/prefer-single-boolean-return': 'error',
59 | 'sonarjs/no-identical-expressions': 'error',
60 | 'sonarjs/no-useless-catch': 'error',
61 | 'sonarjs/prefer-immediate-return': 'error',
62 | 'sonarjs/no-redundant-jump': 'error',
63 | 'sonarjs/no-same-line-conditional': 'error',
64 | 'sonarjs/no-collapsible-if': 'error',
65 | 'sonarjs/no-inverted-boolean-check': 'error',
66 |
67 | 'sonarjs/cognitive-complexity': ['warn', 15],
68 | 'sonarjs/max-switch-cases': ['warn', 30],
69 | 'sonarjs/no-nested-switch': 'off',
70 | 'sonarjs/no-nested-template-literals': 'warn',
71 | 'sonarjs/no-nested-conditional': 'off',
72 | 'sonarjs/no-nested-functions': 'off',
73 | 'sonarjs/pseudo-random': 'off',
74 |
75 | 'import/order': [
76 | 'error',
77 | {
78 | groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
79 | 'newlines-between': 'always',
80 | alphabetize: {
81 | order: 'asc',
82 | caseInsensitive: true,
83 | },
84 | },
85 | ],
86 | },
87 | },
88 | {
89 | files: ['**/*.ts'],
90 | languageOptions: {
91 | parser: typescriptParser,
92 | parserOptions: {
93 | project: false,
94 | },
95 | },
96 | plugins: {
97 | '@typescript-eslint': typescriptPlugin,
98 | },
99 | rules: {
100 | ...typescriptPlugin.configs.recommended.rules,
101 | 'no-unused-vars': 'off',
102 | '@typescript-eslint/no-unused-vars': [
103 | 'warn',
104 | {
105 | argsIgnorePattern: '^_',
106 | varsIgnorePattern: '^_',
107 | },
108 | ],
109 | '@typescript-eslint/no-explicit-any': 'warn',
110 | '@typescript-eslint/no-require-imports': 'off',
111 | },
112 | },
113 | {
114 | files: ['src/usal.js', 'src/usal.ts'],
115 | rules: {
116 | 'no-console': 'off',
117 | complexity: 'off',
118 | 'max-depth': 'off',
119 | 'max-lines': 'off',
120 | 'sonarjs/cognitive-complexity': 'off',
121 | },
122 | },
123 | {
124 | files: ['src/integrations/react/**/*.{js,ts}'],
125 | languageOptions: {
126 | parserOptions: {
127 | ecmaFeatures: {
128 | jsx: true,
129 | },
130 | },
131 | },
132 | rules: {
133 | 'no-unused-vars': [
134 | 'warn',
135 | {
136 | argsIgnorePattern: '^_',
137 | varsIgnorePattern: '^_|React',
138 | },
139 | ],
140 | },
141 | },
142 | {
143 | files: ['src/integrations/vue/**/*.{js,ts}'],
144 | rules: {
145 | 'no-unused-vars': [
146 | 'warn',
147 | {
148 | argsIgnorePattern: '^_|h',
149 | varsIgnorePattern: '^_|h',
150 | },
151 | ],
152 | },
153 | },
154 | {
155 | files: ['src/integrations/lit/**/*.{js,ts}'],
156 | rules: {
157 | 'no-unused-vars': [
158 | 'warn',
159 | {
160 | argsIgnorePattern: '^_',
161 | varsIgnorePattern: '^_|html|css|LitElement',
162 | },
163 | ],
164 | },
165 | },
166 | {
167 | files: ['src/integrations/angular/**/*.ts'],
168 | rules: {
169 | '@typescript-eslint/no-explicit-any': 'off',
170 | '@typescript-eslint/no-unused-vars': [
171 | 'warn',
172 | {
173 | argsIgnorePattern: '^_',
174 | varsIgnorePattern: '^_|Injectable|Directive|Input',
175 | },
176 | ],
177 | },
178 | },
179 | {
180 | files: ['build.js', 'update-tags.js', 'postbuild.js', '*.config.{js,mjs}'],
181 | rules: {
182 | 'no-console': 'off',
183 | '@typescript-eslint/no-var-requires': 'off',
184 | '@typescript-eslint/no-require-imports': 'off',
185 | },
186 | },
187 | {
188 | files: ['**/*.test.{js,ts}', '**/*.spec.{js,ts}'],
189 | rules: {
190 | 'no-console': 'off',
191 | },
192 | },
193 | {
194 | ignores: [
195 | '**/node_modules/**',
196 | '**/packages/**',
197 | '**/dist/**',
198 | '**/*.min.js',
199 | '**/coverage/**',
200 | '**/*.d.ts',
201 | '**/test/**',
202 | ],
203 | },
204 | ];
205 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "usal-monorepo",
3 | "version": "1.3.1",
4 | "private": true,
5 | "type": "module",
6 | "description": "Ultimate Scroll Animation Library - Lightweight, powerful, wonderfully simple ✨",
7 | "homepage": "https://usal.dev/",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/usaljs/usal.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/usaljs/usal/issues"
14 | },
15 | "author": "Italo Almeida",
16 | "license": "MIT",
17 | "workspaces": [
18 | "packages/*"
19 | ],
20 | "packages": {
21 | "react": {
22 | "dependencies": {
23 | "react": [
24 | ">=16.8.0",
25 | false
26 | ]
27 | },
28 | "jsx": true,
29 | "usage": {
30 | "color": "grey",
31 | "pseudonym": [
32 | "Next.js"
33 | ],
34 | "file": "index.js/layout.ts",
35 | "import": "import { USALProvider } from '@usal/react';",
36 | "start": "{children} ",
37 | "prop": "data-usal=\"VALUES\""
38 | }
39 | },
40 | "solid": {
41 | "dependencies": {
42 | "solid-js": [
43 | ">=1.0.0",
44 | false
45 | ]
46 | },
47 | "usage": {
48 | "color": "blue",
49 | "pseudonym": [
50 | "SolidStart"
51 | ],
52 | "file": "app.tsx",
53 | "import": "import { USALProvider } from '@usal/solid';",
54 | "start": "{props.children} ",
55 | "prop": "data-usal=\"VALUES\""
56 | }
57 | },
58 | "svelte": {
59 | "dependencies": {
60 | "svelte": [
61 | ">=3.0.0",
62 | false
63 | ]
64 | },
65 | "usage": {
66 | "color": "orange",
67 | "pseudonym": [
68 | "SvelteKit"
69 | ],
70 | "file": "App.svelte/+layout.svelte",
71 | "import": "import { usal } from '@usal/svelte';",
72 | "start": "// USAL auto-initializes globally",
73 | "prop": "use:usal={'VALUES'}"
74 | }
75 | },
76 | "vue": {
77 | "dependencies": {
78 | "vue": [
79 | ">=3.0.0",
80 | false
81 | ],
82 | "@nuxt/kit": [
83 | ">=3.0.0",
84 | true
85 | ]
86 | },
87 | "plugins": [
88 | "nuxt",
89 | "nuxt-plugin"
90 | ],
91 | "usage": {
92 | "color": "green",
93 | "pseudonym": [
94 | "Nuxt"
95 | ],
96 | "file": "nuxt.config.ts",
97 | "import": "import { USALPlugin } from '@usal/vue';",
98 | "start": "createApp(App).use(USALPlugin).mount('#app');\n//for Nuxt\nexport default defineNuxtConfig({\nmodules: ['@usal/vue/nuxt']\n//...",
99 | "prop": "v-usal=\"'VALUES'\""
100 | }
101 | },
102 | "lit": {
103 | "dependencies": {
104 | "lit": [
105 | ">=2.0.0",
106 | false
107 | ]
108 | },
109 | "usage": {
110 | "color": "cyan",
111 | "pseudonym": [],
112 | "file": "main.ts",
113 | "import": "import { usal } from '@usal/lit';",
114 | "start": "// USAL auto-initializes globally",
115 | "prop": "${usal('VALUES')}"
116 | }
117 | },
118 | "angular": {
119 | "format": "angular",
120 | "dependencies": {
121 | "@angular/core": [
122 | ">=12.0.0",
123 | false
124 | ],
125 | "@angular/common": [
126 | ">=12.0.0",
127 | false
128 | ]
129 | },
130 | "usage": {
131 | "color": "red",
132 | "pseudonym": [],
133 | "file": "app.component.ts",
134 | "import": "import { USALModule } from '@usal/angular';",
135 | "start": "@Component({imports: [USALModule]})\nexport class AppComponent",
136 | "prop": "usal=\"VALUES\""
137 | }
138 | }
139 | },
140 | "scripts": {
141 | "dev": "node scripts/build.js --watch --skip-angular",
142 | "dev:with-angular": "node scripts/build.js --watch",
143 | "build": "npm run lint:fix && node scripts/build.js",
144 | "release:canary": "node scripts/publish.js --canary",
145 | "release:canary:dry": "node scripts/publish.js --canary --dry-run",
146 | "release:beta": "node scripts/publish.js --beta",
147 | "release:beta:dry": "node scripts/publish.js --beta --dry-run",
148 | "release:latest": "node scripts/publish.js --latest",
149 | "release:latest:dry": "node scripts/publish.js --latest --dry-run",
150 | "release:github": "node scripts/release-gh.js",
151 | "release:github:dry": "node scripts/release-gh.js --dry-run",
152 | "release:complete": "npm run release:latest && npm run release:github",
153 | "release:complete:dry": "npm run release:latest:dry && npm run release:github:dry",
154 | "format": "prettier --write --ignore-path .gitignore **/*.{js,ts,jsx,tsx,html,css} '!test/**' && npm run format:doc",
155 | "format:doc": "prettier --write --ignore-path .prettierignore *.{json,md} packages/**/*.{json,md}",
156 | "lint": "eslint src/**/*.{js,ts,jsx,tsx}",
157 | "lint:fix": "eslint src/**/*.{js,ts,jsx,tsx} --fix",
158 | "lint:pkgs": "npx eslint packages/** --no-ignore --rule {no-var:off,no-redeclare:off,prefer-const:off,eqeqeq:off,import/order:off,no-setter-return:off,no-sparse-arrays:off,no-cond-assign:off}",
159 | "lint:texts": "node scripts/lint-texts.js"
160 | },
161 | "devDependencies": {
162 | "@angular/common": ">=12.0.0",
163 | "@angular/compiler": "^20.2.3",
164 | "@angular/compiler-cli": "^20.2.3",
165 | "@angular/core": ">=12.0.0",
166 | "@nuxt/kit": ">=3.0.0",
167 | "@typescript-eslint/eslint-plugin": "^8.42.0",
168 | "@typescript-eslint/parser": "^8.42.0",
169 | "browserslist-config-baseline": "^0.5.0",
170 | "chokidar": "^4.0.3",
171 | "dotenv-cli": "^10.0.0",
172 | "esbuild": "^0.25.9",
173 | "eslint": "^9.34.0",
174 | "eslint-config-prettier": "^10.1.8",
175 | "eslint-plugin-compat": "^6.0.2",
176 | "eslint-plugin-import": "^2.32.0",
177 | "eslint-plugin-sonarjs": "^3.0.5",
178 | "globals": "^16.3.0",
179 | "langdetect": "^0.2.1",
180 | "lit": ">=2.0.0",
181 | "ng-packagr": "^20.2.0",
182 | "nuxt": ">=3.0.0",
183 | "prettier": "^3.6.2",
184 | "react": ">=16.8.0",
185 | "solid-js": ">=1.0.0",
186 | "svelte": ">=3.0.0",
187 | "typescript": "^5.0.0",
188 | "vue": ">=3.0.0"
189 | },
190 | "keywords": [
191 | "javascript",
192 | "animation",
193 | "css-animations",
194 | "web-animation",
195 | "scroll-animations",
196 | "intersection-observer",
197 | "scroll-lib"
198 | ],
199 | "browserslist": "extends browserslist-config-baseline"
200 | }
201 |
--------------------------------------------------------------------------------
/scripts/colorize.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable sonarjs/cognitive-complexity */
2 | const colors = {
3 | reset: '\x1b[0m',
4 | bright: '\x1b[1m',
5 | dim: '\x1b[2m',
6 | underscore: '\x1b[4m',
7 | blink: '\x1b[5m',
8 | reverse: '\x1b[7m',
9 | hidden: '\x1b[8m',
10 |
11 | black: '\x1b[30m',
12 | red: '\x1b[31m',
13 | green: '\x1b[32m',
14 | yellow: '\x1b[33m',
15 | blue: '\x1b[34m',
16 | magenta: '\x1b[35m',
17 | cyan: '\x1b[36m',
18 | white: '\x1b[37m',
19 |
20 | brightBlack: '\x1b[90m',
21 | brightRed: '\x1b[91m',
22 | brightGreen: '\x1b[92m',
23 | brightYellow: '\x1b[93m',
24 | brightBlue: '\x1b[94m',
25 | brightMagenta: '\x1b[95m',
26 | brightCyan: '\x1b[96m',
27 | brightWhite: '\x1b[97m',
28 | };
29 |
30 | const calculateBgColor = (colorCode) => {
31 | const match = colorCode.match(/(\d\d)m$/);
32 | const colorNum = parseInt(match[1]);
33 | if ((colorNum >= 30 && colorNum <= 37) || (colorNum >= 90 && colorNum <= 97))
34 | return `\x1b[${colorNum + 10}m`;
35 | return '\x1b[40m';
36 | };
37 |
38 | const colorMap = {
39 | header: colors.cyan,
40 | success: colors.green,
41 | warning: colors.yellow,
42 | error: colors.red,
43 | info: colors.blue,
44 | highlight: colors.magenta,
45 | tag: colors.blue,
46 | package: colors.brightMagenta,
47 | version: colors.brightCyan,
48 | file: colors.cyan,
49 | command: colors.yellow,
50 | dim: colors.dim,
51 | accent: colors.brightMagenta,
52 | divider: colors.dim,
53 | update: colors.yellow,
54 |
55 | red: colors.red,
56 | green: colors.green,
57 | yellow: colors.yellow,
58 | blue: colors.blue,
59 | magenta: colors.magenta,
60 | cyan: colors.cyan,
61 | white: colors.white,
62 | black: colors.black,
63 | brightRed: colors.brightRed,
64 | brightGreen: colors.brightGreen,
65 | brightYellow: colors.brightYellow,
66 | brightBlue: colors.brightBlue,
67 | brightMagenta: colors.brightMagenta,
68 | brightCyan: colors.brightCyan,
69 | brightWhite: colors.brightWhite,
70 | brightBlack: colors.brightBlack,
71 | };
72 |
73 | export const colorize = (text, parentColor = '') => {
74 | let result = '';
75 | let i = 0;
76 |
77 | while (i < text.length) {
78 | if (text.slice(i, i + 2) === '/#') {
79 | const isTag = text[i + 2] === '[';
80 | const colorStart = i + (isTag ? 3 : 2);
81 | const colorEnd = text.indexOf(' ', colorStart);
82 |
83 | if (colorEnd === -1) {
84 | result += text[i];
85 | i++;
86 | continue;
87 | }
88 |
89 | const colorName = text.slice(colorStart, colorEnd);
90 | const contentStart = colorEnd + 1;
91 | let depth = 1;
92 | let pos = contentStart;
93 |
94 | while (pos < text.length && depth > 0) {
95 | if (text.slice(pos, pos + 2) === '/#') {
96 | depth++;
97 | pos += 2;
98 | } else if (text.slice(pos, pos + 3) === ' #/') {
99 | depth--;
100 | if (depth === 0) break;
101 | pos += 3;
102 | } else {
103 | pos++;
104 | }
105 | }
106 |
107 | if (depth === 0) {
108 | const content = text.slice(contentStart, pos);
109 |
110 | const currentColor =
111 | isTag || parentColor.split('m').length === 3
112 | ? calculateBgColor(colorMap[colorName]) + colors.black
113 | : colorMap[colorName];
114 | const processedContent = colorize(content, currentColor);
115 |
116 | if (currentColor) {
117 | let resetCode = colors.reset;
118 | resetCode += parentColor;
119 | result += `${currentColor}${processedContent}${resetCode}`;
120 | } else {
121 | result += processedContent;
122 | }
123 |
124 | i = pos + 3;
125 | } else {
126 | result += text[i];
127 | i++;
128 | }
129 | } else {
130 | result += text[i];
131 | i++;
132 | }
133 | }
134 |
135 | return result;
136 | };
137 |
138 | export const colorLog = (text) => console.log(colorize(text));
139 | export const hLog = (indentation, isTag, color, header = '', text = '', prefix = '') => {
140 | let logMethod;
141 | switch (color) {
142 | case 'error':
143 | logMethod = console.error;
144 | break;
145 | case 'warning':
146 | logMethod = console.warn;
147 | break;
148 | default:
149 | logMethod = console.log;
150 | }
151 |
152 | if (isTag) header = ' ' + header.toUpperCase() + ' ';
153 |
154 | logMethod(
155 | ' '.repeat(indentation) +
156 | colorize(`${prefix}/#${isTag ? '[' : ''}${color} ${header} #/ ${text}`)
157 | );
158 | };
159 |
--------------------------------------------------------------------------------
/scripts/postbuild.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { execSync } from 'child_process';
3 | import fs from 'fs';
4 | import path from 'path';
5 |
6 | import { hLog } from './colorize.js';
7 |
8 | // Parse arguments
9 | const { projectName, packages, version } = JSON.parse(
10 | Buffer.from(process.argv[2], 'hex').toString('utf8')
11 | );
12 | const isSilent = process.argv.includes('--silent') || process.argv.includes('-s');
13 | if (!isSilent) {
14 | hLog(0, true, 'header', 'postbuild', 'Starting documentation update process...');
15 | hLog(2, false, 'info', 'Project:', `/#accent ${projectName} #/`);
16 | hLog(2, false, 'info', 'Packages:', packages.map((p) => `/#package ${p} #/`).join(', '));
17 | hLog(2, false, 'info', 'Version:', version);
18 | }
19 |
20 | // Track changes
21 | let updateCount = 0;
22 | let errorCount = 0;
23 |
24 | // File paths
25 | const files = {
26 | readme: 'README.md',
27 | index: 'docs/index.html',
28 | api: 'docs/API.md',
29 | vanilla: path.join('packages', 'vanilla', 'README.md'),
30 | };
31 |
32 | // Check README exists
33 | if (!fs.existsSync(files.readme)) {
34 | hLog(2, true, 'error', 'error', 'README.md not found');
35 | process.exit(1);
36 | }
37 |
38 | // Store original states
39 | const originalStates = {};
40 | Object.entries(files).forEach(([key, path]) => {
41 | if (fs.existsSync(path) && key !== 'vanilla') {
42 | originalStates[key] = fs.readFileSync(path, 'utf-8');
43 | }
44 | });
45 |
46 | // Get package config
47 | const { packages: packagesConfig = {} } = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
48 |
49 | // Generic update function
50 | function updateContent(content, updates, fileName) {
51 | if (!isSilent)
52 | hLog(0, true, 'header', `${fileName.toUpperCase()} UPDATE`, `Processing ${fileName}...`, '\n');
53 |
54 | let result = content;
55 | updates.forEach(({ regex, replacement, name }) => {
56 | if (regex.test(result)) {
57 | result = result.replace(regex, replacement);
58 | if (!isSilent) hLog(2, false, 'success', '✓', name);
59 | updateCount++;
60 | } else {
61 | hLog(2, false, 'error', '✗', `${name} not found`);
62 | errorCount++;
63 | }
64 | });
65 |
66 | return result;
67 | }
68 |
69 | // Generate badge URL
70 | function getBadgeUrl(packages = [], useNN = false, singlePackage = null, color = 'black') {
71 | const base = 'https://badge.usal.dev/';
72 | const prefix = useNN ? '?nn&' : '?';
73 |
74 | if (singlePackage) {
75 | return `${base}${prefix}p=${encodeURIComponent(singlePackage)}&color=${color}`;
76 | }
77 | if (packages.length > 0) {
78 | return `${base}${prefix}ps=${encodeURIComponent(packages.join(','))}`;
79 | }
80 | return base;
81 | }
82 |
83 | // Generate framework info
84 | function getFrameworkInfo() {
85 | const frameworkPackages = packages
86 | .filter((pkg) => pkg !== 'vanilla')
87 | .map((pkg) => `@${projectName}/${pkg}`);
88 |
89 | const examples = [
90 | {
91 | framework: 'Vanilla JS',
92 | setup: '',
93 | example: 'Content
',
94 | pseudonyms: [],
95 | },
96 | ];
97 |
98 | Object.entries(packagesConfig).forEach(([framework, config]) => {
99 | if (config.usage && framework !== 'vanilla') {
100 | const name = framework.charAt(0).toUpperCase() + framework.slice(1);
101 | const { import: imp, start, prop, pseudonym = [] } = config.usage;
102 |
103 | examples.push({
104 | framework: name,
105 | setup: [imp, start].filter(Boolean).join('\n').trim(),
106 | example: prop ? `Content
` : '',
107 | pseudonyms: pseudonym,
108 | });
109 | }
110 | });
111 |
112 | return { frameworkPackages, examples };
113 | }
114 |
115 | // Get all updates for a file
116 | function getUpdates(fileType, info) {
117 | const { frameworkPackages, examples } = info;
118 | const updates = [];
119 |
120 | // Common updates
121 | const frameworks = packages.map((p) =>
122 | p === 'vanilla' ? 'Vanilla JS' : p.charAt(0).toUpperCase() + p.slice(1)
123 | );
124 | const sorted = [
125 | ...frameworks.filter((f) => f !== 'Vanilla JS'),
126 | ...(frameworks.includes('Vanilla JS') ? ['Vanilla JS'] : []),
127 | ];
128 | const worksWithText = `Works with ${sorted.join(', ')} and more`;
129 |
130 | if (fileType === 'readme') {
131 | // Update "Works with" section
132 | updates.push({
133 | regex: /\*\*Works with[^*]+\*\*/g,
134 | replacement: `**${worksWithText}**`,
135 | name: 'Works with section',
136 | });
137 |
138 | // NPM installation section
139 | const installCmds = [`npm install ${projectName}`];
140 | const frameworkInstalls = frameworkPackages.map((p) => {
141 | const framework = p.split('/')[1];
142 | const displayName = framework.charAt(0).toUpperCase() + framework.slice(1);
143 | const pseudonyms = packagesConfig[framework]?.usage?.pseudonym || [];
144 | const label = pseudonyms.length > 0 ? `${displayName}/${pseudonyms.join('/')}` : displayName;
145 | return `npm install ${p} # For ${label}`;
146 | });
147 |
148 | if (frameworkInstalls.length > 0) {
149 | installCmds.push('', '# Framework-specific packages', ...frameworkInstalls);
150 | }
151 |
152 | updates.push({
153 | regex: /(### NPM\n\n```bash\n)([\s\S]*?)(```)/,
154 | replacement: `$1${installCmds.join('\n')}\n$3`,
155 | name: 'NPM installation',
156 | });
157 |
158 | // Framework Setup section - Update individual framework sections
159 | examples
160 | .filter((e) => e.setup && e.framework !== 'Vanilla JS')
161 | .forEach(({ framework, setup, pseudonyms }) => {
162 | const frameworkName = framework;
163 | const pseudonymText = pseudonyms.length > 0 ? ` \\(${pseudonyms.join('/')}\\)` : '';
164 |
165 | updates.push({
166 | regex: new RegExp(
167 | `(### [\\u{1F7E0}-\\u{1F7EB}\\u{2B1B}\\u{2B1C}] ${frameworkName}${pseudonymText}\n\n\`\`\`jsx?\n)([\\s\\S]*?)(\`\`\`)`,
168 | 'iu'
169 | ),
170 | replacement: `$1${setup}\n$3`,
171 | name: `${frameworkName} setup`,
172 | });
173 | });
174 |
175 | // Package table in Packages Overview section
176 | let pkgTable = '| Package | Version |\n|---------|---------|\n';
177 | pkgTable += `| \`${projectName}\` | }) |\n`;
178 |
179 | packages
180 | .filter((p) => p !== 'vanilla')
181 | .forEach((pkg) => {
182 | const name = `@${projectName}/${pkg}`;
183 | const color = packagesConfig[pkg]?.usage?.color || 'grey';
184 | pkgTable += `| \`${name}\` | }) |\n`;
185 | });
186 |
187 | updates.push({
188 | regex: /(## 📊 Packages Overview\n\n)([\s\S]*?)(\n\n## )/,
189 | replacement: `$1${pkgTable}$3`,
190 | name: 'Package overview table',
191 | });
192 | }
193 |
194 | if (fileType === 'api' && fs.existsSync(files.api)) {
195 | // Framework Usage section agora em API.md
196 | const usageExamples = examples
197 | .map(({ framework, example, pseudonyms }) => {
198 | if (!example) return '';
199 | const label = pseudonyms.length > 0 ? `${framework}/${pseudonyms.join('/')}` : framework;
200 | return `\n${example}`;
201 | })
202 | .filter(Boolean)
203 | .join('\n\n');
204 |
205 | updates.push({
206 | regex: /(### Framework Usage\n\n```html\n)([\s\S]*?)(```)/,
207 | replacement: `$1${usageExamples}\n$3`,
208 | name: 'Framework usage examples',
209 | });
210 | }
211 |
212 | if (fileType === 'index' && fs.existsSync(files.index)) {
213 | // Installation commands
214 | const installCmds = [`# Install\nnpm install ${projectName}`];
215 | if (frameworkPackages.length > 0) {
216 | installCmds.push(
217 | '\n# Framework-specific packages:',
218 | ...frameworkPackages.map((p) => `npm install ${p}`)
219 | );
220 | }
221 |
222 | updates.push({
223 | regex: /(# Install[\s\S]*?)<\/code><\/pre>/,
224 | replacement: `${installCmds.join('\n')}
`,
225 | name: 'Installation section',
226 | });
227 |
228 | // Usage examples HTML
229 | const htmlExamples = examples
230 | .map(({ framework, example, pseudonyms }) => {
231 | if (!example) return '';
232 | const label = pseudonyms.length > 0 ? `${framework}/${pseudonyms.join('/')}` : framework;
233 | const escapedExample = example.replace(
234 | /[<>"'&]/g,
235 | (m) => ({ '<': '<', '>': '>', '"': '"', "'": ''', '&': '&' })[m]
236 | );
237 | return `<!-- ${label} -->\n${escapedExample}`;
238 | })
239 | .filter(Boolean)
240 | .join('\n\n');
241 |
242 | updates.push({
243 | regex:
244 | /(Main animation attribute<\/div>\s*
)([\s\S]*?)(<\/code><\/pre>)/,
245 | replacement: `$1${htmlExamples}\n$3`,
246 | name: 'Usage examples',
247 | });
248 |
249 | // JavaScript API
250 | const setupExamples = examples
251 | .filter((e) => e.setup && e.framework !== 'Vanilla JS')
252 | .map(({ framework, setup, pseudonyms }) => {
253 | const label = framework + (pseudonyms.length ? ` (${pseudonyms.join(', ')})` : '');
254 | return `// ${label}\n${setup}`;
255 | })
256 | .join('\n\n');
257 |
258 | updates.push({
259 | regex:
260 | /(Installed via CDN, initializes automatically, instance in window\.USAL\n)[\s\S]*?(<\/code><\/pre>)/,
261 | replacement: `$1\n${setupExamples.replace(
262 | /[<>"'&]/g,
263 | (m) => ({ '<': '<', '>': '>', '"': '"', "'": ''', '&': '&' })[m]
264 | )}$2`,
265 | name: 'JavaScript API',
266 | });
267 |
268 | // Badges
269 | updates.push({
270 | regex: /(