use-scroll-animate 1.2.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,214 +1,256 @@
1
- <div align="center">
2
-
3
- # use-scroll-animate 🚀
4
-
5
- **A lightweight (~2.9KB gzipped), dependency-free scroll animation library for the modern web.**
6
-
7
- [![GitHub release (latest by date)](https://img.shields.io/github/v/release/HarrisonCN/use-scroll-animate?style=flat-square)](https://github.com/HarrisonCN/use-scroll-animate/releases)
8
- [![GitHub repo size](https://img.shields.io/github/repo-size/HarrisonCN/use-scroll-animate?style=flat-square)](https://github.com/HarrisonCN/use-scroll-animate)
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
10
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
11
-
12
- </div>
13
-
14
- ## Why `use-scroll-animate`?
15
-
16
- In 2025, performance is everything. Traditional scroll animation libraries often bundle heavy dependencies, rely on outdated scroll event listeners, or force you into a specific framework.
17
-
18
- `use-scroll-animate` is built differently:
19
- - ⚡ **Zero Dependencies**: Pure Vanilla JS/TypeScript.
20
- - 🚀 **High Performance**: Powered by `IntersectionObserver` and the native `Web Animations API`. No scroll event listeners, no layout thrashing.
21
- - ðŸŠķ **Ultra Lightweight**: Only ~2.9KB gzipped.
22
- - ðŸ§Đ **Framework Agnostic**: Works seamlessly with Vanilla JS, React, Vue, Svelte, and more. First-class React Hooks and Vue Composables included.
23
- - â™ŋ **Accessible**: Respects `prefers-reduced-motion` out of the box.
24
-
25
- ## Installation
26
-
27
- ```bash
28
- npm install use-scroll-animate
29
- # or
30
- yarn add use-scroll-animate
31
- # or
32
- pnpm add use-scroll-animate
33
- ```
34
-
35
- ## Quick Start (Vanilla JS / HTML)
36
-
37
- The easiest way to use it is via HTML `data-sa` attributes.
38
-
39
- ```html
40
- <!-- 1. Add data-sa attributes to your elements -->
41
- <div data-sa data-sa-animation="fade-in-up" data-sa-duration="800">
42
- I will animate when scrolled into view!
43
- </div>
44
-
45
- <div data-sa data-sa-animation="zoom-in" data-sa-delay="200">
46
- Me too, with a delay!
47
- </div>
48
-
49
- <script type="module">
50
- // 2. Import and initialize
51
- import ScrollAnimate from 'use-scroll-animate';
52
-
53
- ScrollAnimate.init();
54
- </script>
55
- ```
56
-
57
- ## React Integration
58
-
59
- We provide a dedicated hook for React users.
60
-
61
- ```tsx
62
- import { createReactHooks } from 'use-scroll-animate/react';
63
- import React from 'react';
64
-
65
- const { useScrollAnimate, useScrollStagger } = createReactHooks(React);
66
-
67
- function App() {
68
- // Single element animation
69
- const titleRef = useScrollAnimate({ animation: 'fade-in-down', duration: 1000 });
70
-
71
- // Staggered list animation
72
- const listRef = useScrollStagger({ animation: 'fade-in-up', stagger: 100 });
73
-
74
- return (
75
- <main>
76
- <h1 ref={titleRef}>Welcome to my site</h1>
77
-
78
- <ul ref={listRef}>
79
- <li>Item 1</li>
80
- <li>Item 2</li>
81
- <li>Item 3</li>
82
- </ul>
83
- </main>
84
- );
85
- }
86
- ```
87
-
88
- ## Vue 3 Integration
89
-
90
- First-class support for Vue 3 Composition API.
91
-
92
- ```vue
93
- <template>
94
- <main>
95
- <h1 :ref="el => titleRef = el">Welcome to my site</h1>
96
- </main>
97
- </template>
98
-
99
- <script setup>
100
- import { ref, onMounted, onUnmounted } from 'vue';
101
- import { createVueComposables } from 'use-scroll-animate/vue';
102
-
103
- const { useScrollAnimate } = createVueComposables({ ref, onMounted, onUnmounted });
104
-
105
- const { animateRef: titleRef } = useScrollAnimate({
106
- animation: 'slide-right',
107
- duration: 800
108
- });
109
- </script>
110
- ```
111
-
112
- ## Built-in Animations
113
-
114
- Choose from 19 highly optimized built-in presets:
115
-
116
- - `fade-in`, `fade-in-up`, `fade-in-down`, `fade-in-left`, `fade-in-right`
117
- - `zoom-in`, `zoom-out`
118
- - `slide-up`, `slide-down`, `slide-left`, `slide-right`
119
- - `flip-x`, `flip-y`
120
- - `bounce`, `rotate-in`, `blur-in`
121
- - `skew-in`, `scale-x`, `scale-y`
122
-
123
- ## Configuration Options
124
-
125
- You can pass these options via JavaScript or as `data-sa-*` attributes in HTML.
126
-
127
- | Option | Type | Default | Description |
128
- |--------|------|---------|-------------|
129
- | `animation` | `string` \| `string[]` \| `object` | `['fade-in-up']` | Preset name, array of presets, or custom keyframes |
130
- | `duration` | `number` | `600` | Animation duration in ms |
131
- | `delay` | `number` | `0` | Delay before animation starts in ms |
132
- | `easing` | `string` | `ease` | CSS easing function (`linear`, `ease-out`, `spring`, etc.) |
133
- | `threshold` | `number` \| `number[]` | `0.1` | Intersection threshold (0 to 1) or array for progress |
134
- | `rootMargin` | `string` | `0px` | Root margin for IntersectionObserver |
135
- | `repeat` | `boolean` | `false` | Replay animation every time it enters viewport |
136
- | `stagger` | `number` | `0` | Delay between sibling elements in ms |
137
- | `parallax` | `object` | `{}` | Parallax effect configuration (x, y, rotate, scale, speed) |
138
- | `onProgress` | `(el, progress) => void` | `undefined` | Callback with scroll progress (0 to 1) |
139
-
140
- ## Advanced Usage
141
-
142
- ### Multiple Animations
143
-
144
- Combine multiple built-in presets for richer effects. For example, `['fade-in-up', 'zoom-in']`.
145
-
146
- ```html
147
- <div data-sa data-sa-animation="fade-in-up, zoom-in" data-sa-duration="1200">
148
- I will fade in from bottom and zoom in!
149
- </div>
150
- ```
151
-
152
- ### Parallax Effect
153
-
154
- Apply a parallax effect based on scroll position. Use `data-sa-parallax-x`, `data-sa-parallax-y`, `data-sa-parallax-rotate`, `data-sa-parallax-scale`.
155
-
156
- ```html
157
- <div data-sa data-sa-parallax-y="-100px" data-sa-parallax-speed="0.5">
158
- I will move up 100px as you scroll!
159
- </div>
160
-
161
- <div data-sa data-sa-parallax-rotate="30" data-sa-parallax-speed="0.8">
162
- I will rotate 30 degrees as you scroll!
163
- </div>
164
- ```
165
-
166
- ### Scroll Progress Listener
167
-
168
- Get real-time scroll progress (0 to 1) for an element.
169
-
170
- ```javascript
171
- import ScrollAnimate from 'use-scroll-animate';
172
-
173
- ScrollAnimate.observe('.progress-bar', {
174
- onProgress: (el, progress) => {
175
- (el as HTMLElement).style.width = `${progress * 100}%`;
176
- }
177
- });
178
- ```
179
-
180
- ### Custom Animations
181
-
182
- You can define your own animations using the Web Animations API keyframe format:
183
-
184
- ```javascript
185
- import ScrollAnimate from 'use-scroll-animate';
186
-
187
- ScrollAnimate.observe('.custom-box', {
188
- animation: {
189
- from: { opacity: 0, transform: 'scale(0.5) rotate(-45deg)' },
190
- to: { opacity: 1, transform: 'scale(1) rotate(0deg)' }
191
- },
192
- duration: 1000,
193
- easing: 'spring'
194
- });
195
- ```
196
-
197
- ### Callbacks
198
-
199
- ```javascript
200
- ScrollAnimate.observe('.track-me', {
201
- onStart: (el) => console.log('Animation started on', el),
202
- onComplete: (el) => console.log('Animation finished on', el),
203
- onEnter: (el) => console.log('Element entered viewport'),
204
- onLeave: (el) => console.log('Element left viewport')
205
- });
206
- ```
207
-
208
- ## Contributing
209
-
210
- Contributions are always welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
211
-
212
- ## License
213
-
214
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1
+ <div align="center">
2
+
3
+ # use-scroll-animate 🚀
4
+
5
+ **A lightweight (~5KB gzipped), dependency-free scroll animation library for the modern web.**
6
+
7
+ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/HarrisonCN/use-scroll-animate?style=flat-square)](https://github.com/HarrisonCN/use-scroll-animate/releases)
8
+ [![GitHub repo size](https://img.shields.io/github/repo-size/HarrisonCN/use-scroll-animate?style=flat-square)](https://github.com/HarrisonCN/use-scroll-animate)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
10
+
11
+ [English](./README.md) | [įŪ€ä―“äļ­æ–‡](./README_zh.md) | [æ—ĨæœŽčŠž](./README_ja.md)
12
+
13
+ </div>
14
+
15
+ ## Why `use-scroll-animate`?
16
+
17
+ In 2025, performance is everything. Traditional scroll animation libraries often bundle heavy dependencies, rely on outdated scroll event listeners, or force you into a specific framework.
18
+
19
+ `use-scroll-animate` is built differently:
20
+ - ⚡ **Zero Dependencies**: Pure Vanilla JS/TypeScript.
21
+ - 🚀 **High Performance**: Powered by `IntersectionObserver` and the native `Web Animations API`. No scroll event listeners by default (the opt-in scroll-progress mode uses a single passive, rAF-throttled listener, only while tracked elements are on screen).
22
+ - ðŸŠķ **Ultra Lightweight**: ~4.8KB gzipped for the core (tree-shaken, minified ESM); everything incl. `sequence`, `staggerChildren` and the React/Vue helpers is ~6.1KB (UMD ~6.2KB).
23
+ - ðŸ§Đ **Framework Agnostic**: Works seamlessly with Vanilla JS, React, Vue, Svelte, and more. First-class React Hooks and Vue Composables included.
24
+ - â™ŋ **Accessible**: Respects `prefers-reduced-motion` out of the box (content is shown immediately, no entrance or parallax motion).
25
+ - ðŸ–Ĩïļ **SSR-safe**: Importing (and even calling) the API on the server is a no-op.
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install use-scroll-animate
31
+ ```
32
+
33
+ ## v1.4.0 New Features âœĻ
34
+
35
+ ### True scroll progress (`progressMode: 'scroll'`)
36
+ By default `onProgress` reports the element's *visible ratio*, which never reaches 1 for elements taller than the screen. Opt in to real scroll progress: `0` when the element's top reaches the bottom of the viewport, `1` when its bottom leaves the top.
37
+
38
+ ```js
39
+ ScrollAnimate.observe('.chapter', {
40
+ progressMode: 'scroll',
41
+ onProgress: (el, p) => el.style.setProperty('--progress', p),
42
+ });
43
+ ```
44
+
45
+ Parallax uses the same progress, so `progressMode: 'scroll'` also gives smooth parallax on tall sections. HTML: `data-sa-progress="scroll"`. Also exported as a helper: `getScrollProgress(el, root?)`.
46
+
47
+ ### Stagger dynamically added children
48
+ `staggerChildren()` (vanilla), `useScrollStagger()` (React **and now Vue**) accept `observeChildren: true`. A `MutationObserver` picks up children added later (infinite lists, "load more"): those added before the reveal join the stagger; those added after it animate when they scroll into view, staggered per batch.
49
+
50
+ ```js
51
+ import { staggerChildren } from 'use-scroll-animate';
52
+ const stop = staggerChildren(document.querySelector('#feed'), {
53
+ animation: 'fade-in-up', stagger: 60, observeChildren: true,
54
+ });
55
+ // later: stop();
56
+ ```
57
+
58
+ ### Timelines with `sequence()`
59
+ Chain animations across elements. Each step starts when the previous one ends; `gap` adds a pause (negative values overlap) and `at` sets an absolute start time.
60
+
61
+ ```js
62
+ import { sequence } from 'use-scroll-animate';
63
+
64
+ const tl = sequence([
65
+ { target: '.hero h1', animation: 'fade-in-up', duration: 700 },
66
+ { target: '.hero p', animation: 'blur-in', gap: -300 },
67
+ { target: '.hero .btn', animation: 'scale-up', stagger: 80 },
68
+ ], { trigger: '.hero', easing: 'soft-spring' }); // auto-plays once when .hero enters
69
+
70
+ await tl.play(); // or play manually; resolves when every step is done
71
+ tl.cancel(); // stop and leave everything visible
72
+ ```
73
+
74
+ ### New presets
75
+ `scale-up`, `blur-in-up`, `flip-up`, `flip-down`, `rotate-left`, `rotate-right`, and clip-path reveals `clip-up`, `clip-down`, `clip-left`, `clip-right`, `clip-circle`.
76
+
77
+ ### Smaller memory footprint
78
+ Finished `once` elements are dropped from the registry right after they animate (unless they still need parallax/`onProgress`), so long pages and SPAs don't keep thousands of records alive. They're remembered in a `WeakSet`, so `init()`/`observe()` never replay them. Set `createScrollAnimate({ autoUnregister: false })` to keep them listed in `getObservedElements()` as before.
79
+
80
+ ### Proper `exports` map
81
+ Node ESM (`import`) resolves to `dist/index.mjs`, CommonJS (`require`) to `dist/index.js`, each with matching bundled types. The legacy `main`/`module`/`unpkg` fields and `dist/*` deep imports keep working.
82
+
83
+ ## v1.3.0: Custom Easing ðŸŽĻ
84
+
85
+ You can now use custom cubic-bezier curves or even JavaScript functions to create complex physical effects.
86
+
87
+ ### 1. Cubic-Bezier Array
88
+ Pass an array of 4 numbers to define a custom cubic-bezier curve.
89
+
90
+ ```javascript
91
+ ScrollAnimate.observe('.box', {
92
+ animation: 'fade-in-up',
93
+ easing: [0.68, -0.55, 0.265, 1.55] // Custom bounce effect
94
+ });
95
+ ```
96
+
97
+ ### 2. Custom Easing Function
98
+ Pass a function `(t: number) => number` for complete control over the animation timing.
99
+
100
+ ```javascript
101
+ ScrollAnimate.observe('.box', {
102
+ easing: (t) => t * t * (3 - 2 * t) // Custom smooth-step
103
+ });
104
+ ```
105
+
106
+ ### 3. New Physics Presets
107
+ We've added high-quality physics-based easing presets:
108
+ - `spring`: Standard spring effect.
109
+ - `soft-spring`: Gentle, bouncy entrance.
110
+ - `heavy-bounce`: Dramatic bounce effect.
111
+
112
+ ## Quick Start (Vanilla JS / HTML)
113
+
114
+ ```html
115
+ <div data-sa data-sa-animation="fade-in-up" data-sa-easing="soft-spring">
116
+ I have a soft spring effect!
117
+ </div>
118
+
119
+ <div data-sa data-sa-animation="zoom-in" data-sa-easing="[0.34, 1.56, 0.64, 1]">
120
+ I use a custom cubic-bezier array!
121
+ </div>
122
+
123
+ <script type="module">
124
+ import ScrollAnimate from 'use-scroll-animate';
125
+ ScrollAnimate.init();
126
+ </script>
127
+ ```
128
+
129
+ ## Options
130
+
131
+ | Option | Type | Default | Description |
132
+ |--------|------|---------|-------------|
133
+ | `animation` | `string` \| `string[]` \| `{ from, to }` | `'fade-in-up'` | Preset name, array of presets to combine, or custom keyframes |
134
+ | `duration` | `number` | `600` | Duration in ms |
135
+ | `delay` | `number` | `0` | Delay in ms |
136
+ | `easing` | `string` \| `number[]` \| `function` | `'ease'` | CSS easing, preset (`spring`, `soft-spring`, `heavy-bounce`), cubic-bezier array, or `(t) => number` |
137
+ | `threshold` | `number` \| `number[]` | `0.1` | IntersectionObserver threshold(s) |
138
+ | `rootMargin` | `string` | `'0px'` | IntersectionObserver root margin |
139
+ | `once` | `boolean` | `true` | Animate only the first time the element enters |
140
+ | `repeat` | `boolean` | `false` | Re-hide on leave and replay on every entry |
141
+ | `offset` | `number` | `0` | Trigger the animation this many px after the element enters the viewport |
142
+ | `stagger` | `number` | `0` | Extra delay (ms) per sibling revealed in the same batch |
143
+ | `parallax` | `{ x, y, rotate, scale, speed }` | `{}` | Scroll-driven parallax (keeps running after the entrance animation) |
144
+ | `onStart` / `onComplete` / `onEnter` / `onLeave` | `(el) => void` | – | Lifecycle callbacks |
145
+ | `onProgress` | `(el, progress) => void` | – | Progress (0–1) as the element scrolls — visible ratio, or true scroll progress with `progressMode: 'scroll'` |
146
+ | `progressMode` | `'ratio'` \| `'scroll'` | `'ratio'` | How `onProgress`/parallax progress is measured (`'scroll'`: 0 = top enters at the bottom, 1 = bottom leaves at the top) |
147
+ | `progressVar` | `string` | – | Write progress (0–1, same value as `onProgress`) to this CSS custom property, e.g. `'--sa-progress'`, for scroll-driven effects in plain CSS |
148
+
149
+ Every option is also available as a data attribute: `data-sa-animation`, `data-sa-duration`, `data-sa-delay`, `data-sa-easing`, `data-sa-threshold`, `data-sa-root-margin`, `data-sa-once`, `data-sa-repeat`, `data-sa-offset`, `data-sa-stagger`, `data-sa-progress`, `data-sa-progress-var` (bare attribute = `--sa-progress`), `data-sa-parallax-x|y|rotate|scale|speed`.
150
+
151
+ **Presets:** `fade-in`, `fade-in-up|down|left|right`, `zoom-in`, `zoom-out`, `scale-up`, `flip-x`, `flip-y`, `flip-up`, `flip-down`, `slide-up|down|left|right`, `bounce`, `rotate-in`, `rotate-left`, `rotate-right`, `blur-in`, `blur-in-up`, `skew-in`, `scale-x`, `scale-y`, `clip-up|down|left|right`, `clip-circle`, `shimmer`, `pulse`, `swing`. Combine them with an array, e.g. `['fade-in', 'clip-up']`.
152
+
153
+ **Global config** (`createScrollAnimate(config)` / `configure()`): `defaultAnimation`, `defaultDuration`, `defaultDelay`, `defaultEasing`, `defaultThreshold`, `defaultRootMargin`, `defaultRepeat`, `defaultOnce`, `defaultOffset`, `hiddenClass`, `visibleClass`, `useClassNames`, `disabled`, `root`, `autoUnregister` (default `true`).
154
+
155
+ ### Progress as a CSS variable (`progressVar`)
156
+
157
+ Drive any CSS property from scroll position without writing JavaScript callbacks. The element's progress is written to a custom property on the element itself:
158
+
159
+ ```html
160
+ <div data-sa data-sa-progress="scroll" data-sa-progress-var class="hero">â€Ķ</div>
161
+
162
+ <style>
163
+ @media (prefers-reduced-motion: no-preference) {
164
+ .hero { transform: translateY(calc((1 - var(--sa-progress, 0)) * 60px)); opacity: calc(0.4 + var(--sa-progress, 0)); }
165
+ }
166
+ </style>
167
+ ```
168
+
169
+ ```js
170
+ ScrollAnimate.observe('.bar', { progressVar: '--fill', progressMode: 'scroll' });
171
+ // .bar::after { transform: scaleX(var(--fill, 0)); }
172
+ ```
173
+
174
+ It uses the same rAF-throttled / IntersectionObserver pipeline as `onProgress`, keeps updating after the entrance animation, and is still written under reduced motion (it is data) — guard motion in your CSS with `prefers-reduced-motion` as above.
175
+
176
+ ## Instance API
177
+
178
+ ```js
179
+ import ScrollAnimate, { createScrollAnimate } from 'use-scroll-animate';
180
+
181
+ ScrollAnimate.init(root?); // observe every [data-sa] element (safe to call again after DOM changes)
182
+ const stop = ScrollAnimate.watch(root?); // init() + auto-observe [data-sa] elements added later; stop() to end
183
+ ScrollAnimate.observe(target, opts); // selector, Element, NodeList or Element[]
184
+ ScrollAnimate.unobserve(target); // stop observing (elements that never animated are made visible)
185
+ ScrollAnimate.animate(target, opts); // play an animation right now
186
+ ScrollAnimate.refresh(); // rebuild observers, e.g. after configure({ root })
187
+ ScrollAnimate.configure({ ... }); // update global defaults
188
+ ScrollAnimate.destroy(); // disconnect everything
189
+
190
+ const sa = createScrollAnimate({ root: document.querySelector('#scroller') }); // isolated instance
191
+
192
+ // Helpers (tree-shakeable)
193
+ import { sequence, staggerChildren, getScrollProgress } from 'use-scroll-animate';
194
+ ```
195
+
196
+ ### Watching the DOM (`watch()`)
197
+
198
+ For SPAs, CMS content, infinite lists or anything rendered after page load, `watch()` replaces "call `init()` again after every DOM change":
199
+
200
+ ```js
201
+ import ScrollAnimate from 'use-scroll-animate';
202
+
203
+ const stop = ScrollAnimate.watch(); // or watch(document.querySelector('#app'))
204
+ // [data-sa] elements inserted later — even deep inside a new subtree, or an existing
205
+ // element that gains the data-sa attribute — are observed with their data-sa-* options.
206
+ // Elements removed from the DOM are released; finished `once` elements are never replayed.
207
+ stop(); // stop watching (destroy() also stops every watcher)
208
+ ```
209
+
210
+ It uses a single `MutationObserver` per call and is a no-op on the server or without `MutationObserver`.
211
+
212
+ Via a `<script>` tag (UMD build), the default instance lives at `ScrollAnimate.default`:
213
+
214
+ ```html
215
+ <script src="https://unpkg.com/use-scroll-animate"></script>
216
+ <script>ScrollAnimate.default.init();</script>
217
+ ```
218
+
219
+ ## React & Vue
220
+
221
+ ```jsx
222
+ import React from 'react';
223
+ import { createReactHooks } from 'use-scroll-animate';
224
+ const { useScrollAnimate, useScrollStagger } = createReactHooks(React);
225
+
226
+ function Card() {
227
+ const ref = useScrollAnimate({ animation: 'zoom-in', easing: 'spring' });
228
+ return <div ref={ref}>Hello</div>;
229
+ }
230
+ ```
231
+
232
+ ```js
233
+ import { ref, onMounted, onUnmounted } from 'vue';
234
+ import { createVueComposables } from 'use-scroll-animate';
235
+ const { useScrollAnimate, useScrollStagger } = createVueComposables({ ref, onMounted, onUnmounted });
236
+ const { animateRef } = useScrollAnimate({ animation: 'fade-in-left' });
237
+ const { staggerRef } = useScrollStagger({ stagger: 60, observeChildren: true }); // <ul ref="staggerRef">
238
+ ```
239
+
240
+ ```jsx
241
+ // React: also animate items appended later
242
+ function Feed({ items }) {
243
+ const ref = useScrollStagger({ animation: 'fade-in-up', stagger: 60, observeChildren: true });
244
+ return <ul ref={ref}>{items.map((i) => <li key={i.id}>{i.title}</li>)}</ul>;
245
+ }
246
+ ```
247
+
248
+ Hooks and composables share the core engine, so `once`, `offset`, custom easing functions, parallax and reduced-motion handling behave exactly like the vanilla API.
249
+
250
+ ## Contributing
251
+
252
+ Contributions are always welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
253
+
254
+ ## License
255
+
256
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.