sticky-card-stack 0.1.6 → 0.1.7
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 +17 -17
- package/dist/index.d.ts +4 -4
- package/dist/lib/StickyCard.d.ts +7 -0
- package/dist/lib/StickyCardStack.d.ts +12 -0
- package/dist/lib/types.d.ts +6 -6
- package/dist/lib/useScrollTrigger.d.ts +1 -1
- package/dist/scs.js +17 -17
- package/dist/scs.js.map +1 -1
- package/package.json +1 -1
- package/dist/lib/StickCard.d.ts +0 -7
- package/dist/lib/StickCardStack.d.ts +0 -12
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ React sticky card stack with GSAP ScrollTrigger and Lenis smooth scroll. Build a
|
|
|
8
8
|
npm install sticky-card-stack
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Peer dependencies (install if you don
|
|
11
|
+
Peer dependencies (install if you don't have them):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install react react-dom gsap lenis
|
|
@@ -18,52 +18,52 @@ npm install react react-dom gsap lenis
|
|
|
18
18
|
|
|
19
19
|
1. **Import the library CSS once** (e.g. in your root entry or layout). Without this, cards will not be laid out or styled.
|
|
20
20
|
2. Call **`useScrollTrigger()`** once at your app root (e.g. in your root layout or main component).
|
|
21
|
-
3. Wrap your cards in **`
|
|
21
|
+
3. Wrap your cards in **`StickyCardStack`** and use **`StickyCard`** for each card.
|
|
22
22
|
|
|
23
23
|
```tsx
|
|
24
|
-
import {
|
|
24
|
+
import { StickyCardStack, StickyCard, useScrollTrigger } from 'sticky-card-stack';
|
|
25
25
|
import 'sticky-card-stack/style.css'; // required
|
|
26
26
|
|
|
27
27
|
function App() {
|
|
28
28
|
useScrollTrigger();
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<
|
|
32
|
-
<
|
|
31
|
+
<StickyCardStack scrollHeight={8}>
|
|
32
|
+
<StickyCard
|
|
33
33
|
header={<h2>Card 1</h2>}
|
|
34
34
|
content={<p>Content</p>}
|
|
35
35
|
main={<img src="..." alt="" />}
|
|
36
36
|
/>
|
|
37
|
-
<
|
|
37
|
+
<StickyCard
|
|
38
38
|
header={<h2>Card 2</h2>}
|
|
39
39
|
content={<p>More content</p>}
|
|
40
40
|
main={<img src="..." alt="" />}
|
|
41
41
|
/>
|
|
42
|
-
</
|
|
42
|
+
</StickyCardStack>
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
All library CSS classes use the **`scs-`** prefix (e.g. `scs-wrap`, `scs-stack`, `scs-card-demo`) so they won
|
|
47
|
+
All library CSS classes use the **`scs-`** prefix (e.g. `scs-wrap`, `scs-stack`, `scs-card-demo`) so they won't clash with your own styles. The stack works inside your own layout (e.g. a main column or grid); the CSS is scoped so it won't be overridden by typical container styles.
|
|
48
48
|
|
|
49
49
|
## API
|
|
50
50
|
|
|
51
51
|
### `useScrollTrigger()`
|
|
52
52
|
|
|
53
|
-
Call once at the root of your app. Sets up Lenis smooth scroll and syncs it with GSAP ScrollTrigger. Required for `
|
|
53
|
+
Call once at the root of your app. Sets up Lenis smooth scroll and syncs it with GSAP ScrollTrigger. Required for `StickyCardStack` to work correctly.
|
|
54
54
|
|
|
55
|
-
### `
|
|
55
|
+
### `StickyCardStack`
|
|
56
56
|
|
|
57
57
|
| Prop | Type | Default | Description |
|
|
58
58
|
|-----------------|------------|---------|-------------|
|
|
59
|
-
| `children` | `ReactNode`| — | Card elements (e.g. `
|
|
59
|
+
| `children` | `ReactNode`| — | Card elements (e.g. `StickyCard` components). |
|
|
60
60
|
| `scrollHeight` | `number` | `8` | Height of the scroll area as a multiple of viewport height. |
|
|
61
61
|
| `cardYOffset` | `number` | `5` | Vertical offset between stacked cards (percent). |
|
|
62
62
|
| `cardScaleStep` | `number` | `0.075` | Scale reduction per card (e.g. 0.075 = 7.5% smaller per card). |
|
|
63
63
|
| `className` | `string` | — | Optional class name for the wrapper. |
|
|
64
64
|
| `colorVariants` | `string[]` | — | Optional background colors applied per card by index (cycles if fewer than cards). |
|
|
65
65
|
|
|
66
|
-
### `
|
|
66
|
+
### `StickyCard`
|
|
67
67
|
|
|
68
68
|
Each card can be used in two ways:
|
|
69
69
|
|
|
@@ -77,18 +77,18 @@ Shared prop:
|
|
|
77
77
|
Example with custom layout:
|
|
78
78
|
|
|
79
79
|
```tsx
|
|
80
|
-
<
|
|
80
|
+
<StickyCard colorOverride="#1a1a2e">
|
|
81
81
|
<div className="my-card">…</div>
|
|
82
|
-
</
|
|
82
|
+
</StickyCard>
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
## TypeScript
|
|
86
86
|
|
|
87
87
|
Exported types:
|
|
88
88
|
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
91
|
-
- `
|
|
89
|
+
- `StickyCardStackProps`
|
|
90
|
+
- `StickyCardProps`
|
|
91
|
+
- `STICKY_CARD_STACK_DEFAULTS`
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { StickyCardStack } from './lib/StickyCardStack';
|
|
2
|
+
export { StickyCard } from './lib/StickyCard';
|
|
3
3
|
export { useScrollTrigger } from './lib/useScrollTrigger';
|
|
4
|
-
export type {
|
|
5
|
-
export {
|
|
4
|
+
export type { StickyCardStackProps, StickyCardProps } from './lib/types';
|
|
5
|
+
export { STICKY_CARD_STACK_DEFAULTS } from './lib/types';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { StickyCardProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A single card for use inside StickyCardStack.
|
|
5
|
+
* Pass children; background uses context color from the stack.
|
|
6
|
+
*/
|
|
7
|
+
export declare function StickyCard({ children }: StickyCardProps): ReactElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StickyCardStackProps } from './types';
|
|
2
|
+
/** Injected by StickyCardStack: variant (1–4) and per-card color (from colorVariants or defaults). */
|
|
3
|
+
export interface StickyCardContextValue {
|
|
4
|
+
variant: number;
|
|
5
|
+
color: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const StickyCardVariantContext: import('react').Context<StickyCardContextValue>;
|
|
8
|
+
/**
|
|
9
|
+
* A sticky stack of cards that animate on scroll (GSAP + ScrollTrigger).
|
|
10
|
+
* Use with useScrollTrigger() at app root for smooth scrolling.
|
|
11
|
+
*/
|
|
12
|
+
export declare function StickyCardStack({ children, scrollHeight, cardYOffset, cardScaleStep, className, colorVariants, }: StickyCardStackProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
/** Props for the
|
|
3
|
-
export interface
|
|
2
|
+
/** Props for the StickyCardStack root component */
|
|
3
|
+
export interface StickyCardStackProps {
|
|
4
4
|
/** Card elements; each child is rendered as one stacked card */
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
/**
|
|
@@ -27,12 +27,12 @@ export interface StickCardStackProps {
|
|
|
27
27
|
*/
|
|
28
28
|
colorVariants?: string[];
|
|
29
29
|
}
|
|
30
|
-
/** Props for
|
|
31
|
-
export interface
|
|
30
|
+
/** Props for StickyCard: children only; color comes from StickyCardStack context. */
|
|
31
|
+
export interface StickyCardProps {
|
|
32
32
|
children: ReactNode;
|
|
33
33
|
}
|
|
34
|
-
/** Default values for
|
|
35
|
-
export declare const
|
|
34
|
+
/** Default values for StickyCardStack animation options */
|
|
35
|
+
export declare const STICKY_CARD_STACK_DEFAULTS: {
|
|
36
36
|
readonly scrollHeight: 8;
|
|
37
37
|
readonly cardYOffset: 5;
|
|
38
38
|
readonly cardScaleStep: 0.075;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sets up Lenis smooth scroll and syncs it with GSAP ScrollTrigger.
|
|
3
|
-
* Call once at app root (e.g. in your root layout or App) when using
|
|
3
|
+
* Call once at app root (e.g. in your root layout or App) when using StickyCardStack.
|
|
4
4
|
*/
|
|
5
5
|
export declare function useScrollTrigger(): void;
|
package/dist/scs.js
CHANGED
|
@@ -3,18 +3,18 @@ import { createContext as L, useRef as x, useEffect as R, Children as T, useCont
|
|
|
3
3
|
import e from "gsap";
|
|
4
4
|
import { ScrollTrigger as a } from "gsap/ScrollTrigger";
|
|
5
5
|
import U from "lenis";
|
|
6
|
-
const
|
|
6
|
+
const S = {
|
|
7
7
|
scrollHeight: 8,
|
|
8
8
|
cardYOffset: 5,
|
|
9
9
|
cardScaleStep: 0.075
|
|
10
10
|
}, _ = ["#3d2fa9", "#ff7722", "#ff3d33", "#785f47"], X = { variant: 1, color: _[0] }, E = L(X), A = 4;
|
|
11
11
|
e.registerPlugin(a);
|
|
12
|
-
function
|
|
12
|
+
function Y({
|
|
13
13
|
children: t,
|
|
14
|
-
scrollHeight: r =
|
|
15
|
-
cardYOffset: h =
|
|
16
|
-
cardScaleStep: m =
|
|
17
|
-
className:
|
|
14
|
+
scrollHeight: r = S.scrollHeight,
|
|
15
|
+
cardYOffset: h = S.cardYOffset,
|
|
16
|
+
cardScaleStep: m = S.cardScaleStep,
|
|
17
|
+
className: w,
|
|
18
18
|
colorVariants: d
|
|
19
19
|
}) {
|
|
20
20
|
const k = x(null), P = x(null);
|
|
@@ -40,20 +40,20 @@ function j({
|
|
|
40
40
|
Math.floor(l / i),
|
|
41
41
|
c - 1
|
|
42
42
|
), g = (l - f * i) / i;
|
|
43
|
-
s.forEach((C,
|
|
44
|
-
if (
|
|
43
|
+
s.forEach((C, y) => {
|
|
44
|
+
if (y < f)
|
|
45
45
|
e.set(C, {
|
|
46
46
|
yPercent: -250,
|
|
47
47
|
rotationX: 35
|
|
48
48
|
});
|
|
49
|
-
else if (
|
|
49
|
+
else if (y === f)
|
|
50
50
|
e.set(C, {
|
|
51
51
|
yPercent: e.utils.interpolate(-50, -200, g),
|
|
52
52
|
rotationX: e.utils.interpolate(0, 35, g),
|
|
53
53
|
scale: 1
|
|
54
54
|
});
|
|
55
55
|
else {
|
|
56
|
-
const v =
|
|
56
|
+
const v = y - f, D = (v - g) * h, H = 1 - (v - g) * m;
|
|
57
57
|
e.set(C, {
|
|
58
58
|
yPercent: -50 + D,
|
|
59
59
|
rotationX: 0,
|
|
@@ -74,7 +74,7 @@ function j({
|
|
|
74
74
|
"div",
|
|
75
75
|
{
|
|
76
76
|
ref: k,
|
|
77
|
-
className:
|
|
77
|
+
className: w ? `scs-wrap ${w}` : "scs-wrap",
|
|
78
78
|
style: I,
|
|
79
79
|
children: /* @__PURE__ */ o("section", { className: "scs-stack", children: /* @__PURE__ */ o("div", { ref: P, className: "scs-stack-inner", children: T.map(t, (u, n) => {
|
|
80
80
|
const s = T.count(t), c = n % A + 1, i = d && d.length > 0 ? d[n % d.length] : _[n % A];
|
|
@@ -90,12 +90,12 @@ function j({
|
|
|
90
90
|
}
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
|
-
function
|
|
93
|
+
function j({ children: t }) {
|
|
94
94
|
const { color: r } = O(E);
|
|
95
95
|
return /* @__PURE__ */ o("div", { style: { backgroundColor: r, width: "100%", minHeight: "100%" }, children: t });
|
|
96
96
|
}
|
|
97
97
|
e.registerPlugin(a);
|
|
98
|
-
function
|
|
98
|
+
function q() {
|
|
99
99
|
R(() => {
|
|
100
100
|
const t = new U();
|
|
101
101
|
return t.on("scroll", a.update), e.ticker.add((r) => {
|
|
@@ -118,9 +118,9 @@ function B() {
|
|
|
118
118
|
}, []);
|
|
119
119
|
}
|
|
120
120
|
export {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
S as STICKY_CARD_STACK_DEFAULTS,
|
|
122
|
+
j as StickyCard,
|
|
123
|
+
Y as StickyCardStack,
|
|
124
|
+
q as useScrollTrigger
|
|
125
125
|
};
|
|
126
126
|
//# sourceMappingURL=scs.js.map
|
package/dist/scs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scs.js","sources":["../src/lib/types.ts","../src/lib/StickCardStack.tsx","../src/lib/StickCard.tsx","../src/lib/useScrollTrigger.ts"],"sourcesContent":["import type { ReactNode } from 'react';\n\n/** Props for the StickCardStack root component */\nexport interface StickCardStackProps {\n /** Card elements; each child is rendered as one stacked card */\n children: ReactNode;\n /**\n * Height of the scroll area as a multiple of viewport height.\n * @default 8\n */\n scrollHeight?: number;\n /**\n * Vertical offset between stacked cards (in percent).\n * @default 5\n */\n cardYOffset?: number;\n /**\n * Scale reduction per card in the stack (e.g. 0.075 = 7.5% smaller per card).\n * @default 0.075\n */\n cardScaleStep?: number;\n /** Optional class name for the wrapper element */\n className?: string;\n /**\n * Optional list of background colors (e.g. ['#3d2fa9', '#ff7722']).\n * Applied per card by index (cycles if fewer colors than cards).\n * When not provided, cards use the base CSS variables (.card-1 … .card-4).\n */\n colorVariants?: string[];\n}\n\n/** Props for StickCard: children only; color comes from StickCardStack context. */\nexport interface StickCardProps {\n children: ReactNode;\n}\n\n/** Default values for StickCardStack animation options */\nexport const STICK_CARD_STACK_DEFAULTS = {\n scrollHeight: 8,\n cardYOffset: 5,\n cardScaleStep: 0.075,\n} as const;\n","import { createContext, useEffect, useRef, Children } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport type { StickCardStackProps } from './types';\nimport { STICK_CARD_STACK_DEFAULTS } from './types';\nimport './StickCardStack.css';\n\n/** Injected by StickCardStack: variant (1–4) and per-card color (from colorVariants or defaults). */\nexport interface StickCardContextValue {\n variant: number;\n color: string;\n}\n\nconst DEFAULT_CARD_COLORS: readonly string[] = ['#3d2fa9', '#ff7722', '#ff3d33', '#785f47'];\nconst defaultContextValue: StickCardContextValue = { variant: 1, color: DEFAULT_CARD_COLORS[0] };\nexport const StickCardVariantContext = createContext<StickCardContextValue>(defaultContextValue);\n\nconst VARIANT_COUNT = 4;\n\ngsap.registerPlugin(ScrollTrigger);\n\n/**\n * A sticky stack of cards that animate on scroll (GSAP + ScrollTrigger).\n * Use with useScrollTrigger() at app root for smooth scrolling.\n */\nexport function StickCardStack({\n children,\n scrollHeight = STICK_CARD_STACK_DEFAULTS.scrollHeight,\n cardYOffset = STICK_CARD_STACK_DEFAULTS.cardYOffset,\n cardScaleStep = STICK_CARD_STACK_DEFAULTS.cardScaleStep,\n className,\n colorVariants,\n}: StickCardStackProps) {\n const wrapRef = useRef<HTMLDivElement>(null);\n const innerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const wrap = wrapRef.current;\n const inner = innerRef.current;\n if (!wrap || !inner) return;\n\n const cards = inner.querySelectorAll<HTMLElement>('.scs-card');\n const totalCards = cards.length;\n if (totalCards === 0) return;\n\n const segmentSize = 1 / totalCards;\n\n cards.forEach((card, i) => {\n gsap.set(card, {\n xPercent: -50,\n yPercent: -50 + i * cardYOffset,\n scale: 1 - i * cardScaleStep,\n });\n });\n\n const scrollHeightPx = window.innerHeight * scrollHeight;\n\n const st = ScrollTrigger.create({\n trigger: wrap,\n start: 'top top',\n end: `+=${scrollHeightPx}px`,\n onUpdate(self) {\n const progress = self.progress;\n const activeIndex = Math.min(\n Math.floor(progress / segmentSize),\n totalCards - 1\n );\n const segmentProgress =\n (progress - activeIndex * segmentSize) / segmentSize;\n\n cards.forEach((card, i) => {\n if (i < activeIndex) {\n gsap.set(card, {\n yPercent: -250,\n rotationX: 35,\n });\n } else if (i === activeIndex) {\n gsap.set(card, {\n yPercent: gsap.utils.interpolate(-50, -200, segmentProgress),\n rotationX: gsap.utils.interpolate(0, 35, segmentProgress),\n scale: 1,\n });\n } else {\n const behindIndex = i - activeIndex;\n const currentYOffset = (behindIndex - segmentProgress) * cardYOffset;\n const currentScale =\n 1 - (behindIndex - segmentProgress) * cardScaleStep;\n gsap.set(card, {\n yPercent: -50 + currentYOffset,\n rotationX: 0,\n scale: currentScale,\n });\n }\n });\n },\n });\n\n return () => {\n st.kill();\n };\n }, [scrollHeight, cardYOffset, cardScaleStep]);\n\n const wrapStyle = {\n height: `calc(100svh * ${scrollHeight})`,\n };\n\n return (\n <div\n ref={wrapRef}\n className={className ? `scs-wrap ${className}` : 'scs-wrap'}\n style={wrapStyle}\n >\n <section className=\"scs-stack\">\n <div ref={innerRef} className=\"scs-stack-inner\">\n {Children.map(children, (child, index) => {\n const totalCards = Children.count(children);\n const variant = (index % VARIANT_COUNT) + 1;\n const color =\n colorVariants && colorVariants.length > 0\n ? colorVariants[index % colorVariants.length]\n : DEFAULT_CARD_COLORS[(index % VARIANT_COUNT)];\n return (\n <StickCardVariantContext.Provider value={{ variant, color }}>\n <div\n className=\"scs-card\"\n style={{ zIndex: totalCards - index }}\n >\n {child}\n </div>\n </StickCardVariantContext.Provider>\n );\n })}\n </div>\n </section>\n </div>\n );\n}\n","import { useContext, type ReactElement } from 'react';\nimport { StickCardVariantContext } from './StickCardStack';\nimport type { StickCardProps } from './types';\n\n/**\n * A single card for use inside StickCardStack.\n * Pass children; background uses context color from the stack.\n */\nexport function StickCard({ children }: StickCardProps): ReactElement {\n const { color } = useContext(StickCardVariantContext);\n return (\n <div style={{ backgroundColor: color, width: '100%', minHeight: '100%' }}>\n {children}\n </div>\n );\n}\n","import { useEffect } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport Lenis from 'lenis';\n\ngsap.registerPlugin(ScrollTrigger);\n\n/**\n * Sets up Lenis smooth scroll and syncs it with GSAP ScrollTrigger.\n * Call once at app root (e.g. in your root layout or App) when using StickCardStack.\n */\nexport function useScrollTrigger(): void {\n useEffect(() => {\n const lenis = new Lenis();\n\n lenis.on('scroll', ScrollTrigger.update);\n\n gsap.ticker.add((time) => {\n lenis.raf(time * 1000);\n });\n gsap.ticker.lagSmoothing(0);\n\n ScrollTrigger.scrollerProxy(document.body, {\n scrollTop(value) {\n if (arguments.length && typeof value === 'number') {\n lenis.scrollTo(value, { immediate: true });\n }\n return lenis.scroll;\n },\n getBoundingClientRect() {\n return {\n top: 0,\n left: 0,\n width: window.innerWidth,\n height: window.innerHeight,\n };\n },\n });\n\n return () => {\n gsap.ticker.remove(lenis.raf);\n lenis.destroy();\n };\n }, []);\n}\n"],"names":["STICK_CARD_STACK_DEFAULTS","DEFAULT_CARD_COLORS","defaultContextValue","StickCardVariantContext","createContext","VARIANT_COUNT","gsap","ScrollTrigger","StickCardStack","children","scrollHeight","cardYOffset","cardScaleStep","className","colorVariants","wrapRef","useRef","innerRef","useEffect","wrap","inner","cards","totalCards","segmentSize","card","i","scrollHeightPx","st","self","progress","activeIndex","segmentProgress","behindIndex","currentYOffset","currentScale","wrapStyle","jsx","Children","child","index","variant","color","StickCard","useContext","useScrollTrigger","lenis","Lenis","time","value"],"mappings":";;;;;AAqCO,MAAMA,IAA4B;AAAA,EACvC,cAAc;AAAA,EACd,aAAa;AAAA,EACb,eAAe;AACjB,GC5BMC,IAAyC,CAAC,WAAW,WAAW,WAAW,SAAS,GACpFC,IAA6C,EAAE,SAAS,GAAG,OAAOD,EAAoB,CAAC,EAAA,GAChFE,IAA0BC,EAAqCF,CAAmB,GAEzFG,IAAgB;AAEtBC,EAAK,eAAeC,CAAa;AAM1B,SAASC,EAAe;AAAA,EAC7B,UAAAC;AAAA,EACA,cAAAC,IAAeV,EAA0B;AAAA,EACzC,aAAAW,IAAcX,EAA0B;AAAA,EACxC,eAAAY,IAAgBZ,EAA0B;AAAA,EAC1C,WAAAa;AAAA,EACA,eAAAC;AACF,GAAwB;AACtB,QAAMC,IAAUC,EAAuB,IAAI,GACrCC,IAAWD,EAAuB,IAAI;AAE5C,EAAAE,EAAU,MAAM;AACd,UAAMC,IAAOJ,EAAQ,SACfK,IAAQH,EAAS;AACvB,QAAI,CAACE,KAAQ,CAACC,EAAO;AAErB,UAAMC,IAAQD,EAAM,iBAA8B,WAAW,GACvDE,IAAaD,EAAM;AACzB,QAAIC,MAAe,EAAG;AAEtB,UAAMC,IAAc,IAAID;AAExB,IAAAD,EAAM,QAAQ,CAACG,GAAMC,MAAM;AACzB,MAAAnB,EAAK,IAAIkB,GAAM;AAAA,QACb,UAAU;AAAA,QACV,UAAU,MAAMC,IAAId;AAAA,QACpB,OAAO,IAAIc,IAAIb;AAAA,MAAA,CAChB;AAAA,IACH,CAAC;AAED,UAAMc,IAAiB,OAAO,cAAchB,GAEtCiB,IAAKpB,EAAc,OAAO;AAAA,MAC9B,SAASY;AAAA,MACT,OAAO;AAAA,MACP,KAAK,KAAKO,CAAc;AAAA,MACxB,SAASE,GAAM;AACb,cAAMC,IAAWD,EAAK,UAChBE,IAAc,KAAK;AAAA,UACvB,KAAK,MAAMD,IAAWN,CAAW;AAAA,UACjCD,IAAa;AAAA,QAAA,GAETS,KACHF,IAAWC,IAAcP,KAAeA;AAE3C,QAAAF,EAAM,QAAQ,CAACG,GAAMC,MAAM;AACzB,cAAIA,IAAIK;AACN,YAAAxB,EAAK,IAAIkB,GAAM;AAAA,cACb,UAAU;AAAA,cACV,WAAW;AAAA,YAAA,CACZ;AAAA,mBACQC,MAAMK;AACf,YAAAxB,EAAK,IAAIkB,GAAM;AAAA,cACb,UAAUlB,EAAK,MAAM,YAAY,KAAK,MAAMyB,CAAe;AAAA,cAC3D,WAAWzB,EAAK,MAAM,YAAY,GAAG,IAAIyB,CAAe;AAAA,cACxD,OAAO;AAAA,YAAA,CACR;AAAA,eACI;AACL,kBAAMC,IAAcP,IAAIK,GAClBG,KAAkBD,IAAcD,KAAmBpB,GACnDuB,IACJ,KAAKF,IAAcD,KAAmBnB;AACxC,YAAAN,EAAK,IAAIkB,GAAM;AAAA,cACb,UAAU,MAAMS;AAAA,cAChB,WAAW;AAAA,cACX,OAAOC;AAAA,YAAA,CACR;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IAAA,CACD;AAED,WAAO,MAAM;AACX,MAAAP,EAAG,KAAA;AAAA,IACL;AAAA,EACF,GAAG,CAACjB,GAAcC,GAAaC,CAAa,CAAC;AAE7C,QAAMuB,IAAY;AAAA,IAChB,QAAQ,iBAAiBzB,CAAY;AAAA,EAAA;AAGvC,SACE,gBAAA0B;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAKrB;AAAA,MACL,WAAWF,IAAY,YAAYA,CAAS,KAAK;AAAA,MACjD,OAAOsB;AAAA,MAEP,UAAA,gBAAAC,EAAC,WAAA,EAAQ,WAAU,aACjB,4BAAC,OAAA,EAAI,KAAKnB,GAAU,WAAU,mBAC3B,UAAAoB,EAAS,IAAI5B,GAAU,CAAC6B,GAAOC,MAAU;AACxC,cAAMjB,IAAae,EAAS,MAAM5B,CAAQ,GACpC+B,IAAWD,IAAQlC,IAAiB,GACpCoC,IACJ3B,KAAiBA,EAAc,SAAS,IACpCA,EAAcyB,IAAQzB,EAAc,MAAM,IAC1Cb,EAAqBsC,IAAQlC,CAAc;AACjD,eACE,gBAAA+B,EAACjC,EAAwB,UAAxB,EAAiC,OAAO,EAAE,SAAAqC,GAAS,OAAAC,KAClD,UAAA,gBAAAL;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,QAAQd,IAAaiB,EAAA;AAAA,YAE7B,UAAAD;AAAA,UAAA;AAAA,QAAA,GAEL;AAAA,MAEJ,CAAC,GACH,EAAA,CACF;AAAA,IAAA;AAAA,EAAA;AAGN;AChIO,SAASI,EAAU,EAAE,UAAAjC,KAA0C;AACpE,QAAM,EAAE,OAAAgC,EAAA,IAAUE,EAAWxC,CAAuB;AACpD,SACE,gBAAAiC,EAAC,OAAA,EAAI,OAAO,EAAE,iBAAiBK,GAAO,OAAO,QAAQ,WAAW,OAAA,GAC7D,UAAAhC,EAAA,CACH;AAEJ;ACVAH,EAAK,eAAeC,CAAa;AAM1B,SAASqC,IAAyB;AACvC,EAAA1B,EAAU,MAAM;AACd,UAAM2B,IAAQ,IAAIC,EAAA;AAElB,WAAAD,EAAM,GAAG,UAAUtC,EAAc,MAAM,GAEvCD,EAAK,OAAO,IAAI,CAACyC,MAAS;AACxB,MAAAF,EAAM,IAAIE,IAAO,GAAI;AAAA,IACvB,CAAC,GACDzC,EAAK,OAAO,aAAa,CAAC,GAE1BC,EAAc,cAAc,SAAS,MAAM;AAAA,MACzC,UAAUyC,GAAO;AACf,eAAI,UAAU,UAAU,OAAOA,KAAU,YACvCH,EAAM,SAASG,GAAO,EAAE,WAAW,IAAM,GAEpCH,EAAM;AAAA,MACf;AAAA,MACA,wBAAwB;AACtB,eAAO;AAAA,UACL,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO,OAAO;AAAA,UACd,QAAQ,OAAO;AAAA,QAAA;AAAA,MAEnB;AAAA,IAAA,CACD,GAEM,MAAM;AACX,MAAAvC,EAAK,OAAO,OAAOuC,EAAM,GAAG,GAC5BA,EAAM,QAAA;AAAA,IACR;AAAA,EACF,GAAG,CAAA,CAAE;AACP;"}
|
|
1
|
+
{"version":3,"file":"scs.js","sources":["../src/lib/types.ts","../src/lib/StickyCardStack.tsx","../src/lib/StickyCard.tsx","../src/lib/useScrollTrigger.ts"],"sourcesContent":["import type { ReactNode } from 'react';\n\n/** Props for the StickyCardStack root component */\nexport interface StickyCardStackProps {\n /** Card elements; each child is rendered as one stacked card */\n children: ReactNode;\n /**\n * Height of the scroll area as a multiple of viewport height.\n * @default 8\n */\n scrollHeight?: number;\n /**\n * Vertical offset between stacked cards (in percent).\n * @default 5\n */\n cardYOffset?: number;\n /**\n * Scale reduction per card in the stack (e.g. 0.075 = 7.5% smaller per card).\n * @default 0.075\n */\n cardScaleStep?: number;\n /** Optional class name for the wrapper element */\n className?: string;\n /**\n * Optional list of background colors (e.g. ['#3d2fa9', '#ff7722']).\n * Applied per card by index (cycles if fewer colors than cards).\n * When not provided, cards use the base CSS variables (.card-1 … .card-4).\n */\n colorVariants?: string[];\n}\n\n/** Props for StickyCard: children only; color comes from StickyCardStack context. */\nexport interface StickyCardProps {\n children: ReactNode;\n}\n\n/** Default values for StickyCardStack animation options */\nexport const STICKY_CARD_STACK_DEFAULTS = {\n scrollHeight: 8,\n cardYOffset: 5,\n cardScaleStep: 0.075,\n} as const;\n","import { createContext, useEffect, useRef, Children } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport type { StickyCardStackProps } from './types';\nimport { STICKY_CARD_STACK_DEFAULTS } from './types';\nimport './StickyCardStack.css';\n\n/** Injected by StickyCardStack: variant (1–4) and per-card color (from colorVariants or defaults). */\nexport interface StickyCardContextValue {\n variant: number;\n color: string;\n}\n\nconst DEFAULT_CARD_COLORS: readonly string[] = ['#3d2fa9', '#ff7722', '#ff3d33', '#785f47'];\nconst defaultContextValue: StickyCardContextValue = { variant: 1, color: DEFAULT_CARD_COLORS[0] };\nexport const StickyCardVariantContext = createContext<StickyCardContextValue>(defaultContextValue);\n\nconst VARIANT_COUNT = 4;\n\ngsap.registerPlugin(ScrollTrigger);\n\n/**\n * A sticky stack of cards that animate on scroll (GSAP + ScrollTrigger).\n * Use with useScrollTrigger() at app root for smooth scrolling.\n */\nexport function StickyCardStack({\n children,\n scrollHeight = STICKY_CARD_STACK_DEFAULTS.scrollHeight,\n cardYOffset = STICKY_CARD_STACK_DEFAULTS.cardYOffset,\n cardScaleStep = STICKY_CARD_STACK_DEFAULTS.cardScaleStep,\n className,\n colorVariants,\n}: StickyCardStackProps) {\n const wrapRef = useRef<HTMLDivElement>(null);\n const innerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const wrap = wrapRef.current;\n const inner = innerRef.current;\n if (!wrap || !inner) return;\n\n const cards = inner.querySelectorAll<HTMLElement>('.scs-card');\n const totalCards = cards.length;\n if (totalCards === 0) return;\n\n const segmentSize = 1 / totalCards;\n\n cards.forEach((card, i) => {\n gsap.set(card, {\n xPercent: -50,\n yPercent: -50 + i * cardYOffset,\n scale: 1 - i * cardScaleStep,\n });\n });\n\n const scrollHeightPx = window.innerHeight * scrollHeight;\n\n const st = ScrollTrigger.create({\n trigger: wrap,\n start: 'top top',\n end: `+=${scrollHeightPx}px`,\n onUpdate(self) {\n const progress = self.progress;\n const activeIndex = Math.min(\n Math.floor(progress / segmentSize),\n totalCards - 1\n );\n const segmentProgress =\n (progress - activeIndex * segmentSize) / segmentSize;\n\n cards.forEach((card, i) => {\n if (i < activeIndex) {\n gsap.set(card, {\n yPercent: -250,\n rotationX: 35,\n });\n } else if (i === activeIndex) {\n gsap.set(card, {\n yPercent: gsap.utils.interpolate(-50, -200, segmentProgress),\n rotationX: gsap.utils.interpolate(0, 35, segmentProgress),\n scale: 1,\n });\n } else {\n const behindIndex = i - activeIndex;\n const currentYOffset = (behindIndex - segmentProgress) * cardYOffset;\n const currentScale =\n 1 - (behindIndex - segmentProgress) * cardScaleStep;\n gsap.set(card, {\n yPercent: -50 + currentYOffset,\n rotationX: 0,\n scale: currentScale,\n });\n }\n });\n },\n });\n\n return () => {\n st.kill();\n };\n }, [scrollHeight, cardYOffset, cardScaleStep]);\n\n const wrapStyle = {\n height: `calc(100svh * ${scrollHeight})`,\n };\n\n return (\n <div\n ref={wrapRef}\n className={className ? `scs-wrap ${className}` : 'scs-wrap'}\n style={wrapStyle}\n >\n <section className=\"scs-stack\">\n <div ref={innerRef} className=\"scs-stack-inner\">\n {Children.map(children, (child, index) => {\n const totalCards = Children.count(children);\n const variant = (index % VARIANT_COUNT) + 1;\n const color =\n colorVariants && colorVariants.length > 0\n ? colorVariants[index % colorVariants.length]\n : DEFAULT_CARD_COLORS[(index % VARIANT_COUNT)];\n return (\n <StickyCardVariantContext.Provider value={{ variant, color }}>\n <div\n className=\"scs-card\"\n style={{ zIndex: totalCards - index }}\n >\n {child}\n </div>\n </StickyCardVariantContext.Provider>\n );\n })}\n </div>\n </section>\n </div>\n );\n}\n","import { useContext, type ReactElement } from 'react';\nimport { StickyCardVariantContext } from './StickyCardStack';\nimport type { StickyCardProps } from './types';\n\n/**\n * A single card for use inside StickyCardStack.\n * Pass children; background uses context color from the stack.\n */\nexport function StickyCard({ children }: StickyCardProps): ReactElement {\n const { color } = useContext(StickyCardVariantContext);\n return (\n <div style={{ backgroundColor: color, width: '100%', minHeight: '100%' }}>\n {children}\n </div>\n );\n}\n","import { useEffect } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport Lenis from 'lenis';\n\ngsap.registerPlugin(ScrollTrigger);\n\n/**\n * Sets up Lenis smooth scroll and syncs it with GSAP ScrollTrigger.\n * Call once at app root (e.g. in your root layout or App) when using StickyCardStack.\n */\nexport function useScrollTrigger(): void {\n useEffect(() => {\n const lenis = new Lenis();\n\n lenis.on('scroll', ScrollTrigger.update);\n\n gsap.ticker.add((time) => {\n lenis.raf(time * 1000);\n });\n gsap.ticker.lagSmoothing(0);\n\n ScrollTrigger.scrollerProxy(document.body, {\n scrollTop(value) {\n if (arguments.length && typeof value === 'number') {\n lenis.scrollTo(value, { immediate: true });\n }\n return lenis.scroll;\n },\n getBoundingClientRect() {\n return {\n top: 0,\n left: 0,\n width: window.innerWidth,\n height: window.innerHeight,\n };\n },\n });\n\n return () => {\n gsap.ticker.remove(lenis.raf);\n lenis.destroy();\n };\n }, []);\n}\n"],"names":["STICKY_CARD_STACK_DEFAULTS","DEFAULT_CARD_COLORS","defaultContextValue","StickyCardVariantContext","createContext","VARIANT_COUNT","gsap","ScrollTrigger","StickyCardStack","children","scrollHeight","cardYOffset","cardScaleStep","className","colorVariants","wrapRef","useRef","innerRef","useEffect","wrap","inner","cards","totalCards","segmentSize","card","i","scrollHeightPx","st","self","progress","activeIndex","segmentProgress","behindIndex","currentYOffset","currentScale","wrapStyle","jsx","Children","child","index","variant","color","StickyCard","useContext","useScrollTrigger","lenis","Lenis","time","value"],"mappings":"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"}
|
package/package.json
CHANGED
package/dist/lib/StickCard.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { ReactElement } from 'react';
|
|
2
|
-
import { StickCardProps } from './types';
|
|
3
|
-
/**
|
|
4
|
-
* A single card for use inside StickCardStack.
|
|
5
|
-
* Pass children; background uses context color from the stack.
|
|
6
|
-
*/
|
|
7
|
-
export declare function StickCard({ children }: StickCardProps): ReactElement;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { StickCardStackProps } from './types';
|
|
2
|
-
/** Injected by StickCardStack: variant (1–4) and per-card color (from colorVariants or defaults). */
|
|
3
|
-
export interface StickCardContextValue {
|
|
4
|
-
variant: number;
|
|
5
|
-
color: string;
|
|
6
|
-
}
|
|
7
|
-
export declare const StickCardVariantContext: import('react').Context<StickCardContextValue>;
|
|
8
|
-
/**
|
|
9
|
-
* A sticky stack of cards that animate on scroll (GSAP + ScrollTrigger).
|
|
10
|
-
* Use with useScrollTrigger() at app root for smooth scrolling.
|
|
11
|
-
*/
|
|
12
|
-
export declare function StickCardStack({ children, scrollHeight, cardYOffset, cardScaleStep, className, colorVariants, }: StickCardStackProps): import("react/jsx-runtime").JSX.Element;
|