Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@reelkit/react

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@reelkit/react - npm Package Compare versions

Comparing version
0.1.3
to
0.2.0
+20
dist/lib/ReelContext.d.ts
import { Signal } from '@reelkit/core';
/**
* Values exposed by a parent {@link Reel} component to its children
* via React context. Use {@link useReelContext} to access.
*/
export interface ReelContextValue {
/** The active slide index signal. */
index: Signal<number>;
/** The total slide count signal. */
count: Signal<number>;
/** Navigate to a specific slide. */
goTo: (index: number, animate?: boolean) => Promise<void>;
}
/** @internal */
export declare const ReelContext: import('react').Context<ReelContextValue | null>;
/**
* Returns the {@link ReelContextValue} from the nearest parent
* {@link Reel}, or `null` if none exists.
*/
export declare const useReelContext: () => ReelContextValue | null;
+27
-2
/**
* @module @reelkit/react
*
* React bindings for the Reelkit slider library.
* React bindings for the ReelKit slider library.
*

@@ -17,7 +17,32 @@ * The main component is {@link Reel} — a virtualized, gesture-driven

* dependency.
*
* @example
* ```tsx
* import { useState } from 'react';
* import { Reel, ReelIndicator } from '@reelkit/react';
*
* function App() {
* return (
* <Reel
* count={100}
* style={{ width: '100%', height: '100dvh' }}
* direction="vertical"
* enableWheel
* itemBuilder={(index, _inRange, size) => (
* <div style={{ width: size[0], height: size[1] }}>
* Slide {index + 1}
* </div>
* )}
* >
* <ReelIndicator />
* </Reel>
* );
* }
* ```
*/
export { createSignal, createComputed, reaction, createDeferred, first, last, isEmpty, generate, abs, isNegative, clamp, lerp, extractRange, observeDomEvent, type Signal, type ComputedSignal, type Subscribable, type Listener, type Dispose, type Deferred, } from '@reelkit/core';
export { createSignal, createComputed, reaction, createDeferred, first, last, isEmpty, generate, abs, isNegative, clamp, lerp, extractRange, observeDomEvent, noop, captureFrame, createSharedVideo, createGestureController, type Signal, type ComputedSignal, type Subscribable, type Listener, type Dispose, type Deferred, type SharedVideoConfig, type SharedVideoInstance, type GestureController, type GestureControllerEvents, } from '@reelkit/core';
export { Reel, defaultRangeExtractor, createDefaultKeyExtractorForLoop, type ReelProps, type ReelApi, } from './lib/Reel';
export { ReelIndicator, type ReelIndicatorProps } from './lib/ReelIndicator';
export { Observe, AnimatedObserve, type AnimatedValue } from './lib/Observe';
export { ReelContext, useReelContext, type ReelContextValue, } from './lib/ReelContext';
export { useBodyLock } from './lib/useBodyLock';
+272
-247

@@ -1,39 +0,39 @@

import { reaction as U, animate as q, defaultRangeExtractor as F, first as T, last as P, createSliderController as I, clamp as X } from "@reelkit/core";
import { abs as fe, clamp as he, createComputed as me, createDeferred as ge, createSignal as ye, defaultRangeExtractor as ve, extractRange as be, first as Se, generate as we, isEmpty as Ce, isNegative as Re, last as ze, lerp as pe, observeDomEvent as Ee, reaction as De } from "@reelkit/core";
import { jsx as z, jsxs as Y } from "react/jsx-runtime";
import { useReducer as V, useEffect as w, useRef as k, memo as O, useState as L, useLayoutEffect as _, useMemo as G } from "react";
import { flushSync as K } from "react-dom";
const J = ({
import { reaction as K, animate as J, defaultRangeExtractor as Q, first as O, last as P, createSliderController as Z, createSignal as H, clamp as ee } from "@reelkit/core";
import { abs as Ce, captureFrame as Se, clamp as Re, createComputed as xe, createDeferred as Ee, createGestureController as ze, createSharedVideo as De, createSignal as ke, defaultRangeExtractor as Me, extractRange as $e, first as Te, generate as We, isEmpty as Ae, isNegative as Be, last as Ne, lerp as je, noop as Ie, observeDomEvent as Ve, reaction as pe } from "@reelkit/core";
import { jsx as h, jsxs as te } from "react/jsx-runtime";
import { useReducer as U, useEffect as w, useRef as M, createContext as ne, useContext as G, memo as p, useState as V, useLayoutEffect as oe } from "react";
import { flushSync as q } from "react-dom";
const X = ({
signals: t,
children: e
}) => {
const n = V(() => ({}), {})[1];
return w(() => U(() => t, n), []), e();
}, Q = ({
const n = U(() => ({}), {})[1];
return w(() => K(() => t, n), []), e();
}, re = ({
signal: t,
children: e
}) => {
const n = k(t.value.value), s = V(() => ({}), {})[1], r = k(!1), f = k(null), h = k(void 0);
return w(() => (r.current = !0, () => {
r.current = !1;
const n = M(t.value.value), r = U(() => ({}), {})[1], o = M(!1), u = M(null), d = M(void 0);
return w(() => (o.current = !0, () => {
o.current = !1;
}), []), w(
() => U(
() => K(
() => [t],
() => {
const { value: g, duration: C, done: b } = t.value;
if (f.current) {
f.current(), f.current = null;
const c = h.current;
c && (h.current = void 0, setTimeout(() => c(), 0));
const { value: y, duration: R, done: C } = t.value;
if (u.current) {
u.current(), u.current = null;
const i = d.current;
i && (d.current = void 0, setTimeout(() => i(), 0));
}
if (C > 0) {
h.current = b, f.current = q({
if (R > 0) {
d.current = C, u.current = J({
from: n.current,
to: g,
duration: C,
onUpdate: (c) => {
n.current = c, r.current && K(s);
to: y,
duration: R,
onUpdate: (i) => {
n.current = i, o.current && q(r);
},
onComplete: () => {
f.current = null, h.current = void 0, setTimeout(() => b?.(), 0);
u.current = null, d.current = void 0, setTimeout(() => C?.(), 0);
}

@@ -43,4 +43,4 @@ });

}
h.current = void 0, n.current = g, requestAnimationFrame(() => {
r.current && K(s);
d.current = void 0, n.current = y, requestAnimationFrame(() => {
o.current && q(r);
});

@@ -51,117 +51,119 @@ }

), e(n.current);
}, Z = (t) => t.toString(), se = (t, e) => (n, s) => {
const r = `${e ?? ""}${n}`;
return t === 2 && [0, 1].includes(n) && s === 0 ? `${r}_cloned` : r;
}, H = ({
rangeExtractor: t = F,
}, L = ne(null), he = () => G(L), ce = (t) => t.toString(), me = (t, e) => (n, r) => {
const o = `${e ?? ""}${n}`;
return t === 2 && [0, 1].includes(n) && r === 0 ? `${o}_cloned` : o;
}, ie = ({
rangeExtractor: t = Q,
initialIndex: e = 0,
direction: n = "vertical",
swipeDistanceFactor: s = 0.12,
loop: r = !1,
keyExtractor: f = Z,
useNavKeys: h = !0,
transitionDuration: g = 300,
enableWheel: C = !1,
wheelDebounceMs: b = 200,
...c
swipeDistanceFactor: r = 0.12,
loop: o = !1,
keyExtractor: u = ce,
useNavKeys: d = !0,
transitionDuration: y = 300,
enableWheel: R = !1,
wheelDebounceMs: C = 200,
...i
}) => {
const { size: N, apiRef: y } = c, m = N === void 0, [M, S] = L([0, 0]), d = m ? M : N, v = k(null);
v.current = {
...c,
size: d,
const { size: B, apiRef: b } = i, m = B === void 0, [$, S] = V([0, 0]), f = m ? $ : B, g = M(null);
g.current = {
...i,
size: f,
rangeExtractor: t,
initialIndex: e,
direction: n,
swipeDistanceFactor: s,
loop: r,
keyExtractor: f,
useNavKeys: h,
transitionDuration: g,
enableWheel: C,
wheelDebounceMs: b
swipeDistanceFactor: r,
loop: o,
keyExtractor: u,
useNavKeys: d,
transitionDuration: y,
enableWheel: R,
wheelDebounceMs: C
};
const p = n === "horizontal", E = p ? T(d) : P(d), $ = k(null), [i, j] = L(
() => [
I(
{
count: c.count,
initialIndex: e,
direction: n,
loop: r,
transitionDuration: g,
swipeDistanceFactor: s,
rangeExtractor: t,
enableWheel: C,
wheelDebounceMs: b
const z = n === "horizontal", D = z ? O(f) : P(f), k = M(null), [s, N, j] = V(() => {
const a = Z(
{
count: i.count,
initialIndex: e,
direction: n,
loop: o,
transitionDuration: y,
swipeDistanceFactor: r,
rangeExtractor: t,
enableWheel: R,
wheelDebounceMs: C
},
{
onBeforeChange: (l, v, E) => {
g.current.beforeChange?.(l, v, E);
},
{
onBeforeChange: (o, u, a) => {
v.current.beforeChange?.(o, u, a);
},
onAfterChange: (o, u) => {
v.current.afterChange?.(o, u);
},
onDragStart: (o) => {
v.current.onSlideDragStart?.(o);
},
onDragEnd: (o) => {
v.current.onSlideDragEnd?.(o);
},
onDragCanceled: (o) => {
v.current.onSlideDragCanceled?.(o);
}
onAfterChange: (l, v) => {
g.current.afterChange?.(l, v);
},
onDragStart: (l) => {
g.current.onSlideDragStart?.(l);
},
onDragEnd: (l) => {
g.current.onSlideDragEnd?.(l);
},
onDragCanceled: (l) => {
g.current.onSlideDragCanceled?.(l);
}
),
(o, u) => {
const { keyExtractor: a, itemBuilder: D, size: W } = v.current;
return /* @__PURE__ */ z("div", { "data-index": o, children: D(o, u, W) }, a(o, u));
}
]
)[0];
), x = {
index: a.state.index,
count: H(i.count),
goTo: a.goTo
};
return [a, (l, v) => {
const { keyExtractor: E, itemBuilder: Y, size: _ } = g.current;
return /* @__PURE__ */ h("div", { "data-index": l, children: Y(l, v, _) }, E(l, v));
}, x];
})[0];
w(() => {
i.updateConfig({
count: c.count,
j.count.value = i.count, s.updateConfig({
count: i.count,
direction: n,
loop: r,
transitionDuration: g,
swipeDistanceFactor: s,
loop: o,
transitionDuration: y,
swipeDistanceFactor: r,
rangeExtractor: t
});
}, [
c.count,
i.count,
n,
o,
y,
r,
g,
s,
t
]), w(() => {
i.setPrimarySize(E);
}, [E]), w(() => {
h ? i.observe() : i.unobserve();
}, [h]), w(() => {
if ($.current && (i.attach($.current), i.observe()), y != null) {
const o = {
next: () => i.next(),
prev: () => i.prev(),
goTo: (u, a) => i.goTo(u, a),
adjust: () => i.adjust(),
observe: () => i.observe(),
unobserve: () => i.unobserve()
s.setPrimarySize(D);
}, [D]), w(() => {
d ? s.observe() : s.unobserve();
}, [d]), w(() => {
if (k.current && (s.attach(k.current), s.observe()), b != null) {
const a = {
next: () => s.next(),
prev: () => s.prev(),
goTo: (x, A) => s.goTo(x, A),
adjust: () => s.adjust(),
observe: () => s.observe(),
unobserve: () => s.unobserve()
};
typeof y == "function" ? y(o) : y.current = o;
typeof b == "function" ? b(a) : b.current = a;
}
return () => {
i.detach();
s.detach();
};
}, []), _(() => {
if (!m || !$.current) return;
const o = $.current, u = () => {
const D = o.clientWidth, W = o.clientHeight;
D > 0 && W > 0 && S(
(B) => B[0] === D && B[1] === W ? B : [D, W]
}, []), oe(() => {
if (!m || !k.current) return;
const a = k.current, x = () => {
const l = a.clientWidth, v = a.clientHeight;
l > 0 && v > 0 && S(
(E) => E[0] === l && E[1] === v ? E : [l, v]
);
}, a = new ResizeObserver(u);
return a.observe(o), () => a.disconnect();
}, A = new ResizeObserver(x);
return A.observe(a), () => A.disconnect();
}, [m]);
const R = {
const I = {
userSelect: "none",

@@ -171,20 +173,20 @@ WebkitUserSelect: "none",

overflow: "hidden",
...m ? {} : { width: T(d), height: P(d) },
...c.style
}, { axisValue: A, indexes: x } = i.state, l = !m || E > 0;
return /* @__PURE__ */ Y("div", { ref: $, className: c.className, style: R, children: [
l && /* @__PURE__ */ z(J, { signals: [x], children: () => /* @__PURE__ */ z(
ee,
...m ? {} : { width: O(f), height: P(f) },
...i.style
}, { axisValue: c, indexes: T } = s.state, W = !m || D > 0;
return /* @__PURE__ */ h(L.Provider, { value: j, children: /* @__PURE__ */ te("div", { ref: k, className: i.className, style: I, children: [
W && /* @__PURE__ */ h(X, { signals: [T], children: () => /* @__PURE__ */ h(
se,
{
primarySize: E,
isHorizontal: p,
axisValue: A,
length: x.value.length,
children: x.value.map(j)
primarySize: D,
isHorizontal: z,
axisValue: c,
length: T.value.length,
children: T.value.map(N)
}
) }),
c.children
] });
}, ce = O(
H,
i.children
] }) });
}, ge = p(
ie,
(t, e) => (

@@ -195,3 +197,3 @@ // useEffect deps (controller config sync)

)
), ee = O(
), se = p(
({

@@ -201,5 +203,5 @@ children: t,

primarySize: n,
axisValue: s,
length: r
}) => /* @__PURE__ */ z(Q, { signal: s, children: (f) => /* @__PURE__ */ z(
axisValue: r,
length: o
}) => /* @__PURE__ */ h(re, { signal: r, children: (u) => /* @__PURE__ */ h(
"div",

@@ -212,5 +214,5 @@ {

display: "flex",
transform: `translate${e ? "X" : "Y"}(${f}px)`,
transform: `translate${e ? "X" : "Y"}(${u}px)`,
flexDirection: e ? "row" : "column",
[e ? "width" : "height"]: r * n,
[e ? "width" : "height"]: o * n,
[e ? "height" : "width"]: "100%"

@@ -221,108 +223,125 @@ },

) })
), te = (t) => {
), F = (t) => {
const {
count: e,
active: n,
direction: s = "vertical",
visible: r = 5,
radius: f = 3,
gap: h = 4,
activeColor: g = "#fff",
inactiveColor: C = "rgba(255, 255, 255, 0.5)",
edgeScale: b = 0.5,
className: c,
style: N,
onDotClick: y
} = t, m = s === "vertical", M = f * 2, S = M + h, [d, v] = L(() => e <= r ? 0 : X(n - Math.floor(r / 2), 0, e - r));
direction: r = "vertical",
visible: o = 5,
radius: u = 3,
gap: d = 4,
activeColor: y = "#fff",
inactiveColor: R = "rgba(255, 255, 255, 0.5)",
edgeScale: C = 0.5,
className: i,
style: B,
onDotClick: b
} = t, m = r === "vertical", $ = u * 2, S = $ + d, [f, g] = V(() => e <= o ? 0 : ee(n - Math.floor(o / 2), 0, e - o));
w(() => {
if (e <= r) {
v(0);
if (e <= o) {
g(0);
return;
}
v((R) => n < R ? Math.max(0, n) : n >= R + r ? Math.min(e - r, n - r + 1) : R);
}, [n, e, r]);
const p = Math.min(d + r, e), E = d > 0;
let i = Math.min(r, e) * S;
e > r && (i += S * 2);
const j = G(() => {
const R = [], A = Math.max(0, d - 1), x = Math.min(e, p + 1);
for (let l = A; l < x; l++) {
const o = l === n;
let u = 1;
(l < d || l >= p) && (u = b);
let a;
l < d ? a = 0 : l >= p ? a = r + 1 : a = l - d + 1, !E && a > 0 && (a -= 1);
const D = a * S;
R.push(
/* @__PURE__ */ z(
"span",
{
"data-reel-indicator": l,
style: {
position: "absolute",
[m ? "top" : "left"]: D,
[m ? "left" : "top"]: 0,
width: S,
height: S,
display: "flex",
justifyContent: "center",
alignItems: "center",
transition: "top 0.2s ease, left 0.2s ease"
},
onClick: y ? () => y(l) : void 0,
"data-testid": `indicator-dot-${l}`,
children: /* @__PURE__ */ z(
"span",
{
style: {
width: M,
height: M,
borderRadius: "50%",
backgroundColor: o ? g : C,
transition: "transform 0.2s ease, background-color 0.2s ease",
transform: `scale(${u})`,
cursor: y ? "pointer" : "default"
}
g((c) => n < c ? Math.max(0, n) : n >= c + o ? Math.min(e - o, n - o + 1) : c);
}, [n, e, o]);
const z = Math.min(f + o, e), D = f > 0;
let s = Math.min(o, e) * S;
e > o && (s += S * 2);
const N = [], j = Math.max(0, f - 1), I = Math.min(e, z + 1);
for (let c = j; c < I; c++) {
const T = c === n;
let W = 1;
(c < f || c >= z) && (W = C);
let a;
c < f ? a = 0 : c >= z ? a = o + 1 : a = c - f + 1, !D && a > 0 && (a -= 1);
const x = a * S;
N.push(
/* @__PURE__ */ h(
"span",
{
"data-reel-indicator": c,
style: {
position: "absolute",
[m ? "top" : "left"]: x,
[m ? "left" : "top"]: 0,
width: S,
height: S,
display: "flex",
justifyContent: "center",
alignItems: "center",
transition: "top 0.2s ease, left 0.2s ease"
},
onClick: b ? () => b(c) : void 0,
"data-testid": `indicator-dot-${c}`,
children: /* @__PURE__ */ h(
"span",
{
style: {
width: $,
height: $,
borderRadius: "50%",
backgroundColor: T ? y : R,
transition: "transform 0.2s ease, background-color 0.2s ease",
transform: `scale(${W})`,
cursor: b ? "pointer" : "default"
}
)
},
l
)
);
}
return R;
}, [
d,
p,
E,
r,
e,
n,
M,
S,
b,
g,
C,
m,
y
]);
return /* @__PURE__ */ z(
}
)
},
c
)
);
}
return /* @__PURE__ */ h(
"div",
{
className: c,
className: i,
style: {
position: "relative",
overflow: "hidden",
[m ? "height" : "width"]: i,
[m ? "height" : "width"]: s,
[m ? "width" : "height"]: S,
...N
...B
},
children: j
children: N
}
);
}, ae = O(te), le = (t) => {
}, ae = (t) => {
const e = G(L), n = t.active !== void 0, r = t.count !== void 0;
if (!n && !e)
throw new Error(
'ReelIndicator: "active" prop is required when rendered outside a <Reel> component.'
);
if (!r && !e)
throw new Error(
'ReelIndicator: "count" prop is required when rendered outside a <Reel> component.'
);
if (n && r)
return /* @__PURE__ */ h(
F,
{
...t,
active: t.active,
count: t.count
}
);
const o = [
!n && e.index,
!r && e.count
].filter(Boolean), u = t.onDotClick ?? ((d) => {
e.goTo(d, !0);
});
return /* @__PURE__ */ h(X, { signals: o, children: () => /* @__PURE__ */ h(
F,
{
...t,
active: t.active ?? e.index.value,
count: t.count ?? e.count.value,
onDotClick: u
}
) });
}, ve = p(ae), ye = (t) => {
w(() => {
if (!t) return;
const e = document.body.style.overflow, n = document.body.style.paddingRight, s = window.innerWidth - document.documentElement.clientWidth;
return document.body.style.overflow = "hidden", s > 0 && (document.body.style.paddingRight = `${s}px`), () => {
const e = document.body.style.overflow, n = document.body.style.paddingRight, r = window.innerWidth - document.documentElement.clientWidth;
return document.body.style.overflow = "hidden", r > 0 && (document.body.style.paddingRight = `${r}px`), () => {
document.body.style.overflow = e, document.body.style.paddingRight = n;

@@ -333,23 +352,29 @@ };

export {
Q as AnimatedObserve,
J as Observe,
ce as Reel,
ae as ReelIndicator,
fe as abs,
he as clamp,
me as createComputed,
se as createDefaultKeyExtractorForLoop,
ge as createDeferred,
ye as createSignal,
ve as defaultRangeExtractor,
be as extractRange,
Se as first,
we as generate,
Ce as isEmpty,
Re as isNegative,
ze as last,
pe as lerp,
Ee as observeDomEvent,
De as reaction,
le as useBodyLock
re as AnimatedObserve,
X as Observe,
ge as Reel,
L as ReelContext,
ve as ReelIndicator,
Ce as abs,
Se as captureFrame,
Re as clamp,
xe as createComputed,
me as createDefaultKeyExtractorForLoop,
Ee as createDeferred,
ze as createGestureController,
De as createSharedVideo,
ke as createSignal,
Me as defaultRangeExtractor,
$e as extractRange,
Te as first,
We as generate,
Ae as isEmpty,
Be as isNegative,
Ne as last,
je as lerp,
Ie as noop,
Ve as observeDomEvent,
pe as reaction,
ye as useBodyLock,
he as useReelContext
};

@@ -8,7 +8,14 @@ import { CSSProperties } from 'react';

export interface ReelIndicatorProps {
/** Total number of items in the slider. */
count: number;
/** Index of the currently active item. */
active: number;
/**
* Total number of items in the slider.
* Auto-connected from parent Reel when omitted.
*/
count?: number;
/**
* Index of the currently active item.
* Auto-connected from parent Reel when omitted.
* When provided, takes precedence over the context value.
*/
active?: number;
/**
* Axis along which dots are arranged.

@@ -59,7 +66,2 @@ * @default 'vertical'

}
/**
* Instagram-style scrolling dot indicator. Shows a sliding window of
* normal-sized dots with smaller edge dots indicating overflow.
* The window slides smoothly as the active index changes.
*/
export declare const ReelIndicator: import('react').NamedExoticComponent<ReelIndicatorProps>;
{
"name": "@reelkit/react",
"version": "0.1.3",
"version": "0.2.0",
"type": "module",

@@ -55,3 +55,3 @@ "sideEffects": false,

"dependencies": {
"@reelkit/core": "^0.1.2"
"@reelkit/core": ">=0.1.2"
},

@@ -58,0 +58,0 @@ "peerDependencies": {

+11
-12

@@ -5,7 +5,7 @@ # @reelkit/react

<a href="https://www.npmjs.com/package/@reelkit/react"><img src="https://img.shields.io/npm/v/@reelkit/react?color=6366f1&label=npm" alt="npm" /></a>
<img src="https://img.shields.io/badge/gzip-2.6%20kB-6366f1" alt="Bundle size" />
<img src="https://img.shields.io/badge/gzip-2.9%20kB-6366f1" alt="Bundle size" />
<a href="https://github.com/KonstantinKai/reelkit"><img src="https://img.shields.io/github/stars/KonstantinKai/reelkit?style=social" alt="Star on GitHub" /></a>
</p>
React components and hooks for building TikTok/Instagram Reels-style sliders. Virtualized rendering, touch gestures, keyboard/wheel navigation — all in ~2.6 kB gzip.
React bindings for `@reelkit/core`. Drop in a `<Reel>` component, give it a slide count and a render function — it handles virtualization, gestures, and keyboard/wheel input. ~2.9 kB gzip.

@@ -44,10 +44,9 @@ ## Installation

- **`<Reel>`** — virtualized slider component (only 3 slides in DOM)
- **`<ReelIndicator>`** — Instagram-style position dots
- **Auto-size** — measures container via ResizeObserver, no explicit size props needed
- **Touch gestures** — swipe with momentum and snap
- **Keyboard & wheel** — arrow keys and scroll navigation
- **Loop mode** — infinite circular scrolling
- **SSR ready** — works with Next.js, Remix, and any SSR setup
- **TypeScript** — full type safety
- `<Reel>` — virtualized slider, keeps only 3 slides in the DOM
- `<ReelIndicator>` — dot indicators that auto-connect to the parent `<Reel>` via context
- Measures its own size via ResizeObserver — no width/height props needed
- Swipe with momentum and snap, keyboard arrows, mouse wheel
- Loop mode for infinite circular scrolling
- SSR compatible (Next.js, Remix, etc.)
- Typed with TypeScript, no `@types` package needed

@@ -86,7 +85,7 @@ ## API

Full API reference, interactive demos, and guides at **[reelkit.dev](https://reelkit.dev)**.
API reference, demos, and guides at **[reelkit.dev](https://reelkit.dev)**.
## Support
If you find ReelKit useful, give it a star on GitHub — it helps others discover the project and keeps development going.
If ReelKit saved you some time, a star on GitHub would mean a lot — it's a small thing, but it really helps the project get noticed.

@@ -93,0 +92,0 @@ [![Star on GitHub](https://img.shields.io/github/stars/KonstantinKai/reelkit?style=social)](https://github.com/KonstantinKai/reelkit)