react-manga-effects 0.1.0 → 0.1.6

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 erutobusiness
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 erutobusiness
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.
package/README.md CHANGED
@@ -1,168 +1,168 @@
1
- # 🎬 react-manga-effects
2
-
3
- > Add dramatic anime/manga impact effects to your React applications with zero fuss.
4
-
5
- ![npm](https://img.shields.io/npm/v/@erutobusiness/react-manga-effects)
6
- ![license](https://img.shields.io/npm/l/@erutobusiness/react-manga-effects)
7
- ![size](https://img.shields.io/bundlephobia/minzip/@erutobusiness/react-manga-effects)
8
-
9
- **react-manga-effects** provides lightweight, customizable high-impact visual effects common in anime and manga, such as circular iris wipes and dynamic focus lines. Built with TypeScript and optimizing for performance.
10
-
11
- ## Demo
12
-
13
- ![Demo](./demo.gif)
14
- *(Placeholder: Effects demo appearing here)*
15
-
16
- ## Features
17
-
18
- - **🌀 IrisWipe**: Classic anime-style circular transition to open/close scenes.
19
- - **⚡ SpeedLines**: Dynamic focus/concentration lines (motion lines) to emphasize action or shock.
20
- - **📘 TypeScript Support**: Fully typed props and exports.
21
- - **🎈 Lightweight**: Zero runtime dependencies (other than React).
22
- - **🚀 SSR Compatible**: Works with Next.js, Remix, and Gatsby.
23
- - **🎨 Highly Customizable**: Control colors, density, speed, easing, and positioning.
24
-
25
- ## Installation
26
-
27
- ```bash
28
- npm install @erutobusiness/react-manga-effects
29
- # or
30
- yarn add @erutobusiness/react-manga-effects
31
- # or
32
- pnpm add @erutobusiness/react-manga-effects
33
- ```
34
-
35
- ## Quick Start
36
-
37
- Import the components and use them in your React app:
38
-
39
- ```tsx
40
- import React, { useState } from 'react';
41
- import { IrisWipe, SpeedLines } from '@erutobusiness/react-manga-effects';
42
-
43
- const App = () => {
44
- const [isOpen, setIsOpen] = useState(true);
45
-
46
- return (
47
- <div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
48
- {/* Background Content */}
49
- <img src="/my-manga-scene.jpg" alt="Scene" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
50
-
51
- {/* Overlay Effects */}
52
- <SpeedLines
53
- animated
54
- color="rgba(0,0,0,0.5)"
55
- center={{ x: 50, y: 50 }}
56
- />
57
-
58
- <IrisWipe
59
- isOpen={isOpen}
60
- duration={1000}
61
- center={{ x: 50, y: 50 }}
62
- onComplete={() => console.log('Transition Complete')}
63
- >
64
- {/* Content to be revealed/hidden inside the wipe can go here if needed,
65
- typically IrisWipe is used as an overlay. */}
66
- </IrisWipe>
67
-
68
- <button
69
- onClick={() => setIsOpen(!isOpen)}
70
- style={{ position: 'absolute', bottom: 20, left: 20, zIndex: 100 }}
71
- >
72
- Toggle Transition
73
- </button>
74
- </div>
75
- );
76
- };
77
-
78
- export default App;
79
- ```
80
-
81
- ## Components API
82
-
83
- ### IrisWipe
84
-
85
- A circular masking transition effect.
86
-
87
- | Prop | Type | Default | Description |
88
- |------|------|---------|-------------|
89
- | `isOpen` | `boolean` | **Required** | `true` to reveal content (open iris), `false` to hide (close iris). |
90
- | `duration` | `number` | `500` | Animation duration in milliseconds. |
91
- | `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | Center point of the iris in percentage (0-100). |
92
- | `easing` | `string` | `'easeInOut'` | CSS transition timing function (e.g., `'linear'`, `'ease-out'`, `'cubic-bezier(...)`'). |
93
- | `onComplete` | `() => void` | `undefined` | Callback function fired when the transition finishes. |
94
- | `className` | `string` | `''` | Additional CSS classes for the container. |
95
- | `style` | `CSSProperties` | `{}` | Inline styles for the container. |
96
-
97
- #### Example: Custom Center
98
- ```tsx
99
- <IrisWipe
100
- isOpen={show}
101
- center={{ x: 80, y: 20 }} // Focus on top-right
102
- duration={1200}
103
- />
104
- ```
105
-
106
- ### SpeedLines
107
-
108
- Concentration lines typically used to show speed, shock, or intense focus.
109
-
110
- | Prop | Type | Default | Description |
111
- |------|------|---------|-------------|
112
- | `lineCount` | `number` | `60` | Number of lines to draw. Higher values create a denser effect. |
113
- | `color` | `string` | `'rgba(0, 0, 0, 0.6)'` | Color of the lines. Supports valid CSS colors. |
114
- | `minLength` | `number` | `10` | Minimum length of lines as a percentage of the container size. |
115
- | `maxLength` | `number` | `30` | Maximum length of lines as a percentage of the container size. |
116
- | `innerRadius` | `number` | `0` | Radius of the empty safe zone in the center (percentage). |
117
- | `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | The convergence point of the lines. |
118
- | `animated` | `boolean` | `false` | If `true`, lines will animate (opacity pulse/shake). |
119
- | `animationSpeed`| `number` | `1` | Speed multiplier for the animation. |
120
- | `className` | `string` | `''` | Additional CSS classes. |
121
- | `style` | `CSSProperties` | `{}` | Inline styles. |
122
-
123
- #### Example: Intense Action
124
- ```tsx
125
- <SpeedLines
126
- lineCount={120}
127
- color="red"
128
- innerRadius={10}
129
- animated={true}
130
- animationSpeed={1.5}
131
- />
132
- ```
133
-
134
- ## Storybook
135
-
136
- We use Storybook for development and testing.
137
-
138
- 1. Clone the repository
139
- 2. Install dependencies: `npm install`
140
- 3. Run Storybook:
141
- ```bash
142
- npm run storybook
143
- ```
144
- 4. Open [http://localhost:6006](http://localhost:6006) to view the components.
145
-
146
- ## Development
147
-
148
- Contributions are welcome!
149
-
150
- 1. Clone the repo:
151
- ```bash
152
- git clone https://github.com/erutobusiness/react-manga-effects.git
153
- cd react-manga-effects
154
- ```
155
-
156
- 2. Install dependencies:
157
- ```bash
158
- npm install
159
- ```
160
-
161
- 3. Start development server (Storybook):
162
- ```bash
163
- npm run storybook
164
- ```
165
-
166
- ## License
167
-
1
+ # 🎬 react-manga-effects
2
+
3
+ > Add dramatic anime/manga impact effects to your React applications with zero fuss.
4
+
5
+ ![npm](https://img.shields.io/npm/v/react-manga-effects)
6
+ ![license](https://img.shields.io/npm/l/react-manga-effects)
7
+ ![size](https://img.shields.io/bundlephobia/minzip/react-manga-effects)
8
+ [![CI](https://github.com/erutobusiness/react-manga-effects/actions/workflows/ci.yml/badge.svg)](https://github.com/erutobusiness/react-manga-effects/actions/workflows/ci.yml)
9
+
10
+ **react-manga-effects** provides lightweight, customizable high-impact visual effects common in anime and manga, such as circular iris wipes and dynamic focus lines. Built with TypeScript and optimizing for performance.
11
+
12
+ ## Demo
13
+
14
+ ![Demo](./demo.webp)
15
+
16
+ ## Features
17
+
18
+ - **🌀 IrisWipe**: Classic anime-style circular transition to open/close scenes.
19
+ - **⚡ SpeedLines**: Dynamic focus/concentration lines (motion lines) to emphasize action or shock.
20
+ - **📘 TypeScript Support**: Fully typed props and exports.
21
+ - **🎈 Lightweight**: Zero runtime dependencies (other than React).
22
+ - **🚀 SSR Compatible**: Works with Next.js, Remix, and Gatsby.
23
+ - **🎨 Highly Customizable**: Control colors, density, speed, easing, and positioning.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install react-manga-effects
29
+ # or
30
+ yarn add react-manga-effects
31
+ # or
32
+ pnpm add react-manga-effects
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ Import the components and use them in your React app:
38
+
39
+ ```tsx
40
+ import React, { useState } from 'react';
41
+ import { IrisWipe, SpeedLines } from 'react-manga-effects';
42
+
43
+ const App = () => {
44
+ const [isOpen, setIsOpen] = useState(true);
45
+
46
+ return (
47
+ <div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
48
+ {/* Background Content */}
49
+ <img src="/my-manga-scene.jpg" alt="Scene" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
50
+
51
+ {/* Overlay Effects */}
52
+ <SpeedLines
53
+ animated
54
+ color="rgba(0,0,0,0.5)"
55
+ center={{ x: 50, y: 50 }}
56
+ />
57
+
58
+ <IrisWipe
59
+ isOpen={isOpen}
60
+ duration={1000}
61
+ center={{ x: 50, y: 50 }}
62
+ onComplete={() => console.log('Transition Complete')}
63
+ >
64
+ {/* Content to be revealed/hidden inside the wipe can go here if needed,
65
+ typically IrisWipe is used as an overlay. */}
66
+ </IrisWipe>
67
+
68
+ <button
69
+ onClick={() => setIsOpen(!isOpen)}
70
+ style={{ position: 'absolute', bottom: 20, left: 20, zIndex: 100 }}
71
+ >
72
+ Toggle Transition
73
+ </button>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default App;
79
+ ```
80
+
81
+ ## Components API
82
+
83
+ ### IrisWipe
84
+
85
+ A circular masking transition effect.
86
+
87
+ | Prop | Type | Default | Description |
88
+ |------|------|---------|-------------|
89
+ | `isOpen` | `boolean` | **Required** | `true` to reveal content (open iris), `false` to hide (close iris). |
90
+ | `duration` | `number` | `500` | Animation duration in milliseconds. |
91
+ | `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | Center point of the iris in percentage (0-100). |
92
+ | `easing` | `string` | `'easeInOut'` | CSS transition timing function (e.g., `'linear'`, `'ease-out'`, `'cubic-bezier(...)`'). |
93
+ | `onComplete` | `() => void` | `undefined` | Callback function fired when the transition finishes. |
94
+ | `className` | `string` | `''` | Additional CSS classes for the container. |
95
+ | `style` | `CSSProperties` | `{}` | Inline styles for the container. |
96
+
97
+ #### Example: Custom Center
98
+ ```tsx
99
+ <IrisWipe
100
+ isOpen={show}
101
+ center={{ x: 80, y: 20 }} // Focus on top-right
102
+ duration={1200}
103
+ />
104
+ ```
105
+
106
+ ### SpeedLines
107
+
108
+ Concentration lines typically used to show speed, shock, or intense focus.
109
+
110
+ | Prop | Type | Default | Description |
111
+ |------|------|---------|-------------|
112
+ | `lineCount` | `number` | `60` | Number of lines to draw. Higher values create a denser effect. |
113
+ | `color` | `string` | `'rgba(0, 0, 0, 0.6)'` | Color of the lines. Supports valid CSS colors. |
114
+ | `minLength` | `number` | `10` | Minimum length of lines as a percentage of the container size. |
115
+ | `maxLength` | `number` | `30` | Maximum length of lines as a percentage of the container size. |
116
+ | `innerRadius` | `number` | `0` | Radius of the empty safe zone in the center (percentage). |
117
+ | `center` | `{ x: number, y: number }` | `{ x: 50, y: 50 }` | The convergence point of the lines. |
118
+ | `animated` | `boolean` | `false` | If `true`, lines will animate (opacity pulse/shake). |
119
+ | `animationSpeed`| `number` | `1` | Speed multiplier for the animation. |
120
+ | `className` | `string` | `''` | Additional CSS classes. |
121
+ | `style` | `CSSProperties` | `{}` | Inline styles. |
122
+
123
+ #### Example: Intense Action
124
+ ```tsx
125
+ <SpeedLines
126
+ lineCount={120}
127
+ color="red"
128
+ innerRadius={10}
129
+ animated={true}
130
+ animationSpeed={1.5}
131
+ />
132
+ ```
133
+
134
+ ## Storybook
135
+
136
+ We use Storybook for development and testing.
137
+
138
+ 1. Clone the repository
139
+ 2. Install dependencies: `npm install`
140
+ 3. Run Storybook:
141
+ ```bash
142
+ npm run storybook
143
+ ```
144
+ 4. Open [http://localhost:6006](http://localhost:6006) to view the components.
145
+
146
+ ## Development
147
+
148
+ Contributions are welcome!
149
+
150
+ 1. Clone the repo:
151
+ ```bash
152
+ git clone https://github.com/erutobusiness/react-manga-effects.git
153
+ cd react-manga-effects
154
+ ```
155
+
156
+ 2. Install dependencies:
157
+ ```bash
158
+ npm install
159
+ ```
160
+
161
+ 3. Start development server (Storybook):
162
+ ```bash
163
+ npm run storybook
164
+ ```
165
+
166
+ ## License
167
+
168
168
  MIT © [erutobusiness](https://github.com/erutobusiness)
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("react/jsx-runtime"),a=require("react"),$=({text:e="Manga Effect Placeholder"})=>I.jsx("div",{style:{padding:"2rem",border:"4px dashed #333",textAlign:"center",fontFamily:"sans-serif",fontWeight:"bold",backgroundColor:"#f0f0f0"},children:e}),j=({children:e,isOpen:s,center:n={x:50,y:50},duration:c=500,easing:i="easeInOut",onComplete:o,className:t="",style:r})=>{const E=a.useRef(null);let p="ease-in-out";if(typeof i=="string")switch(i){case"linear":p="linear";break;case"easeIn":p="ease-in";break;case"easeOut":p="ease-out";break;case"easeInOut":p="ease-in-out";break;default:p=i}const u=M=>{M.propertyName==="clip-path"&&o&&o()},y={"--iris-cx":`${n.x}%`,"--iris-cy":`${n.y}%`,"--iris-duration":`${c}ms`,"--iris-easing":p,...r};return I.jsx("div",{ref:E,className:`iris-wipe ${t}`,"data-state":s?"open":"closed",style:y,onTransitionEnd:u,children:e})},D=(e,s)=>{const n=a.useRef(),c=a.useRef(),i=o=>{if(c.current!==void 0){const t=o-c.current;e(t)}c.current=o,n.current=requestAnimationFrame(i)};a.useEffect(()=>(s?n.current=requestAnimationFrame(i):(n.current&&cancelAnimationFrame(n.current),c.current=void 0),()=>{n.current&&cancelAnimationFrame(n.current)}),[s,e])},W=(e,s,n,c)=>{const i=Math.cos(s),o=Math.sin(s);let t=1/0;if(Math.abs(i)>1e-4)if(i>0){const r=(n-e.x)/i;r>=0&&(t=Math.min(t,r))}else{const r=(0-e.x)/i;r>=0&&(t=Math.min(t,r))}if(Math.abs(o)>1e-4)if(o>0){const r=(c-e.y)/o;r>=0&&(t=Math.min(t,r))}else{const r=(0-e.y)/o;r>=0&&(t=Math.min(t,r))}return t===1/0?0:t},q=e=>Array.from({length:e.lineCount},()=>({angle:Math.random()*Math.PI*2,length:(e.minLength+Math.random()*(e.maxLength-e.minLength))/100,width:2+Math.random()*8,opacity:.6+Math.random()*.4,pulseOffset:Math.random()*Math.PI*2,pulseSpeed:2+Math.random()*3})),F=(e,s,n,c,i,o,t,r,E=0,p=!1,u=1)=>{const y=c*(i/100);let M=s.opacity;if(p){const P=.5+(Math.sin(E*s.pulseSpeed*u+s.pulseOffset)+1)*.25;M*=P}const m=W(n,s.angle,t,r)+2;let g=m-m*s.length;if(g<y&&(g=y),g>=m)return;const T=Math.cos(s.angle),R=Math.sin(s.angle),b=n.x+T*m,h=n.y+R*m,l=n.x+T*g,f=n.y+R*g,d=-R,w=T,x=s.width/2;e.beginPath(),e.moveTo(b+d*x,h+w*x),e.lineTo(b-d*x,h-w*x),e.lineTo(l,f),e.closePath(),e.fillStyle=o,e.globalAlpha=M,e.fill(),e.globalAlpha=1},C=({center:e={x:50,y:50},lineCount:s=60,color:n="rgba(0, 0, 0, 0.6)",minLength:c=10,maxLength:i=30,innerRadius:o=0,animated:t=!1,animationSpeed:r=1,className:E="",style:p})=>{const u=a.useRef(null),y=a.useRef(null),M=a.useRef([]),S=a.useRef(e),m=a.useRef(0),[g,T]=a.useState({width:0,height:0});a.useEffect(()=>{S.current=e},[e.x,e.y]),a.useEffect(()=>{M.current=q({lineCount:s,minLength:c,maxLength:i})},[s,c,i]);const R=a.useCallback(()=>{if(y.current&&u.current){const{clientWidth:h,clientHeight:l}=y.current,f=window.devicePixelRatio||1;if(u.current.width!==h*f||u.current.height!==l*f){u.current.width=h*f,u.current.height=l*f,u.current.style.width=`${h}px`,u.current.style.height=`${l}px`;const d=u.current.getContext("2d");d&&d.scale(f,f),T({width:h,height:l})}}},[]);a.useEffect(()=>{R();const h=new ResizeObserver(R);return y.current&&h.observe(y.current),()=>h.disconnect()},[R]);const b=a.useCallback(h=>{const l=u.current,f=y.current,d=l==null?void 0:l.getContext("2d");if(!l||!f||!d||g.width===0)return;d.save(),d.setTransform(1,0,0,1,0,0),d.clearRect(0,0,l.width,l.height),d.restore();const w=f.clientWidth,x=f.clientHeight,v={x:S.current.x/100*w,y:S.current.y/100*x},P=Math.max(v.x,w-v.x),A=Math.max(v.y,x-v.y),O=Math.hypot(P,A);t&&(m.current+=h*.001),M.current.forEach(k=>{F(d,k,v,O,o,n,w,x,m.current,t,r)})},[n,o,t,r,g]);return D(b,t),a.useEffect(()=>{t||b(0)},[b,t,s,c,i,o,n,g]),I.jsx("div",{ref:y,className:`speed-lines-container ${E}`,style:{width:"100%",height:"100%",position:"relative",overflow:"hidden",pointerEvents:"none",...p},children:I.jsx("canvas",{ref:u,style:{width:"100%",height:"100%",display:"block"}})})};exports.IrisWipe=j;exports.Placeholder=$;exports.SpeedLines=C;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("react/jsx-runtime"),a=require("react"),$=({text:e="Manga Effect Placeholder"})=>I.jsx("div",{style:{padding:"2rem",border:"4px dashed #333",textAlign:"center",fontFamily:"sans-serif",fontWeight:"bold",backgroundColor:"#f0f0f0"},children:e}),j=({children:e,isOpen:s,center:n={x:50,y:50},duration:c=1e3,easing:i="easeInOut",onComplete:o,className:t="",style:r})=>{const E=a.useRef(null);let p="ease-in-out";if(typeof i=="string")switch(i){case"linear":p="linear";break;case"easeIn":p="ease-in";break;case"easeOut":p="ease-out";break;case"easeInOut":p="ease-in-out";break;default:p=i}const u=M=>{M.propertyName==="clip-path"&&o&&o()},y={"--iris-cx":`${n.x}%`,"--iris-cy":`${n.y}%`,"--iris-duration":`${c}ms`,"--iris-easing":p,...r};return I.jsx("div",{ref:E,className:`iris-wipe ${t}`,"data-state":s?"open":"closed",style:y,onTransitionEnd:u,children:e})},D=(e,s)=>{const n=a.useRef(),c=a.useRef(),i=o=>{if(c.current!==void 0){const t=o-c.current;e(t)}c.current=o,n.current=requestAnimationFrame(i)};a.useEffect(()=>(s?n.current=requestAnimationFrame(i):(n.current&&cancelAnimationFrame(n.current),c.current=void 0),()=>{n.current&&cancelAnimationFrame(n.current)}),[s,e])},W=(e,s,n,c)=>{const i=Math.cos(s),o=Math.sin(s);let t=1/0;if(Math.abs(i)>1e-4)if(i>0){const r=(n-e.x)/i;r>=0&&(t=Math.min(t,r))}else{const r=(0-e.x)/i;r>=0&&(t=Math.min(t,r))}if(Math.abs(o)>1e-4)if(o>0){const r=(c-e.y)/o;r>=0&&(t=Math.min(t,r))}else{const r=(0-e.y)/o;r>=0&&(t=Math.min(t,r))}return t===1/0?0:t},q=e=>Array.from({length:e.lineCount},()=>({angle:Math.random()*Math.PI*2,length:(e.minLength+Math.random()*(e.maxLength-e.minLength))/100,width:2+Math.random()*8,opacity:.6+Math.random()*.4,pulseOffset:Math.random()*Math.PI*2,pulseSpeed:2+Math.random()*3})),F=(e,s,n,c,i,o,t,r,E=0,p=!1,u=1)=>{const y=c*(i/100);let M=s.opacity;if(p){const P=.5+(Math.sin(E*s.pulseSpeed*u+s.pulseOffset)+1)*.25;M*=P}const m=W(n,s.angle,t,r)+2;let g=m-m*s.length;if(g<y&&(g=y),g>=m)return;const T=Math.cos(s.angle),R=Math.sin(s.angle),b=n.x+T*m,h=n.y+R*m,l=n.x+T*g,f=n.y+R*g,d=-R,w=T,x=s.width/2;e.beginPath(),e.moveTo(b+d*x,h+w*x),e.lineTo(b-d*x,h-w*x),e.lineTo(l,f),e.closePath(),e.fillStyle=o,e.globalAlpha=M,e.fill(),e.globalAlpha=1},C=({center:e={x:50,y:50},lineCount:s=60,color:n="rgba(0, 0, 0, 0.6)",minLength:c=10,maxLength:i=30,innerRadius:o=0,animated:t=!1,animationSpeed:r=1,className:E="",style:p})=>{const u=a.useRef(null),y=a.useRef(null),M=a.useRef([]),S=a.useRef(e),m=a.useRef(0),[g,T]=a.useState({width:0,height:0});a.useEffect(()=>{S.current=e},[e.x,e.y]),a.useEffect(()=>{M.current=q({lineCount:s,minLength:c,maxLength:i})},[s,c,i]);const R=a.useCallback(()=>{if(y.current&&u.current){const{clientWidth:h,clientHeight:l}=y.current,f=window.devicePixelRatio||1;if(u.current.width!==h*f||u.current.height!==l*f){u.current.width=h*f,u.current.height=l*f,u.current.style.width=`${h}px`,u.current.style.height=`${l}px`;const d=u.current.getContext("2d");d&&d.scale(f,f),T({width:h,height:l})}}},[]);a.useEffect(()=>{R();const h=new ResizeObserver(R);return y.current&&h.observe(y.current),()=>h.disconnect()},[R]);const b=a.useCallback(h=>{const l=u.current,f=y.current,d=l==null?void 0:l.getContext("2d");if(!l||!f||!d||g.width===0)return;d.save(),d.setTransform(1,0,0,1,0,0),d.clearRect(0,0,l.width,l.height),d.restore();const w=f.clientWidth,x=f.clientHeight,v={x:S.current.x/100*w,y:S.current.y/100*x},P=Math.max(v.x,w-v.x),A=Math.max(v.y,x-v.y),O=Math.hypot(P,A);t&&(m.current+=h*.001),M.current.forEach(k=>{F(d,k,v,O,o,n,w,x,m.current,t,r)})},[n,o,t,r,g]);return D(b,t),a.useEffect(()=>{t||b(0)},[b,t,s,c,i,o,n,g]),I.jsx("div",{ref:y,className:`speed-lines-container ${E}`,style:{width:"100%",height:"100%",position:"relative",overflow:"hidden",pointerEvents:"none",...p},children:I.jsx("canvas",{ref:u,style:{width:"100%",height:"100%",display:"block"}})})};exports.IrisWipe=j;exports.Placeholder=$;exports.SpeedLines=C;
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ const H = ({ text: t = "Manga Effect Placeholder" }) => /* @__PURE__ */ S("div",
11
11
  children: t,
12
12
  isOpen: s,
13
13
  center: n = { x: 50, y: 50 },
14
- duration: a = 500,
14
+ duration: a = 1e3,
15
15
  easing: i = "easeInOut",
16
16
  onComplete: o,
17
17
  className: e = "",
package/package.json CHANGED
@@ -1,78 +1,78 @@
1
- {
2
- "name": "react-manga-effects",
3
- "version": "0.1.0",
4
- "description": "Lightweight React components for manga/anime-style visual effects - iris wipes and speed lines",
5
- "keywords": [
6
- "react",
7
- "manga",
8
- "anime",
9
- "effects",
10
- "iris-wipe",
11
- "speed-lines",
12
- "focus-lines",
13
- "transition",
14
- "animation",
15
- "typescript"
16
- ],
17
- "homepage": "https://github.com/erutobusiness/react-manga-effects#readme",
18
- "bugs": {
19
- "url": "https://github.com/erutobusiness/react-manga-effects/issues"
20
- },
21
- "repository": {
22
- "type": "git",
23
- "url": "git+https://github.com/erutobusiness/react-manga-effects.git"
24
- },
25
- "license": "MIT",
26
- "author": "erutobusiness",
27
- "sideEffects": [
28
- "*.css"
29
- ],
30
- "files": [
31
- "dist",
32
- "README.md",
33
- "LICENSE"
34
- ],
35
- "type": "module",
36
- "main": "./dist/index.cjs",
37
- "module": "./dist/index.js",
38
- "types": "./dist/index.d.ts",
39
- "exports": {
40
- ".": {
41
- "import": "./dist/index.js",
42
- "require": "./dist/index.cjs",
43
- "types": "./dist/index.d.ts"
44
- },
45
- "./style.css": "./dist/style.css"
46
- },
47
- "scripts": {
48
- "dev": "vite",
49
- "build": "tsc && vite build",
50
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
51
- "preview": "vite preview",
52
- "storybook": "storybook dev -p 6006",
53
- "build-storybook": "storybook build",
54
- "test": "vitest"
55
- },
56
- "peerDependencies": {
57
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
58
- "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
59
- },
60
- "devDependencies": {
61
- "@storybook/react": "^8.0.0",
62
- "@storybook/react-vite": "^8.0.0",
63
- "@testing-library/jest-dom": "^6.9.1",
64
- "@testing-library/react": "^16.3.1",
65
- "@types/node": "^20.0.0",
66
- "@types/react": "^18.2.0",
67
- "@types/react-dom": "^18.2.0",
68
- "@vitejs/plugin-react": "^4.2.0",
69
- "jsdom": "^27.0.1",
70
- "react": "^18.2.0",
71
- "react-dom": "^18.2.0",
72
- "storybook": "^8.0.0",
73
- "typescript": "^5.2.0",
74
- "vite": "^5.0.0",
75
- "vite-plugin-dts": "^3.0.0",
76
- "vitest": "^1.0.0"
77
- }
78
- }
1
+ {
2
+ "name": "react-manga-effects",
3
+ "version": "0.1.6",
4
+ "description": "Lightweight React components for manga/anime-style visual effects - iris wipes and speed lines",
5
+ "keywords": [
6
+ "react",
7
+ "manga",
8
+ "anime",
9
+ "effects",
10
+ "iris-wipe",
11
+ "speed-lines",
12
+ "focus-lines",
13
+ "transition",
14
+ "animation",
15
+ "typescript"
16
+ ],
17
+ "homepage": "https://github.com/erutobusiness/react-manga-effects#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/erutobusiness/react-manga-effects/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/erutobusiness/react-manga-effects.git"
24
+ },
25
+ "license": "MIT",
26
+ "author": "erutobusiness",
27
+ "sideEffects": [
28
+ "*.css"
29
+ ],
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "type": "module",
36
+ "main": "./dist/index.cjs",
37
+ "module": "./dist/index.js",
38
+ "types": "./dist/index.d.ts",
39
+ "exports": {
40
+ ".": {
41
+ "import": "./dist/index.js",
42
+ "require": "./dist/index.cjs",
43
+ "types": "./dist/index.d.ts"
44
+ },
45
+ "./style.css": "./dist/style.css"
46
+ },
47
+ "scripts": {
48
+ "dev": "vite",
49
+ "build": "tsc && vite build",
50
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
51
+ "preview": "vite preview",
52
+ "storybook": "storybook dev -p 6006",
53
+ "build-storybook": "storybook build",
54
+ "test": "vitest"
55
+ },
56
+ "peerDependencies": {
57
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
58
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@storybook/react": "^8.0.0",
62
+ "@storybook/react-vite": "^8.0.0",
63
+ "@testing-library/jest-dom": "^6.9.1",
64
+ "@testing-library/react": "^16.3.1",
65
+ "@types/node": "^20.0.0",
66
+ "@types/react": "^18.2.0",
67
+ "@types/react-dom": "^18.2.0",
68
+ "@vitejs/plugin-react": "^4.2.0",
69
+ "jsdom": "^27.0.1",
70
+ "react": "^18.2.0",
71
+ "react-dom": "^18.2.0",
72
+ "storybook": "^8.0.0",
73
+ "typescript": "^5.2.0",
74
+ "vite": "^5.0.0",
75
+ "vite-plugin-dts": "^3.0.0",
76
+ "vitest": "^1.0.0"
77
+ }
78
+ }