diff --git a/package-lock.json b/package-lock.json
index bb337347..2a101c0b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "react-bits",
"version": "0.0.0",
"dependencies": {
+ "@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^3.20.0",
"@emotion/react": "^11.14.0",
"@gsap/react": "^2.1.2",
@@ -20,6 +21,7 @@
"gsap": "^3.13.0",
"lenis": "^1.3.5",
"maath": "^0.10.8",
+ "mathjs": "^14.6.0",
"matter-js": "^0.20.0",
"meshline": "^3.3.1",
"motion": "^12.23.12",
@@ -472,6 +474,16 @@
"integrity": "sha512-ZqNlhKcZW6MW1LxWIOfh9YVrBykvzyFad3bOh6JJFraDnNa3NXboRDiaI8dmrbb0ZHXCU1Tsq6WQsKV2Vpp5dw==",
"dev": true
},
+ "node_modules/@chakra-ui/icons": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.2.4.tgz",
+ "integrity": "sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@chakra-ui/react": ">=2.0.0",
+ "react": ">=18"
+ }
+ },
"node_modules/@chakra-ui/react": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-3.20.0.tgz",
@@ -4011,7 +4023,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -4342,6 +4354,19 @@
"node": ">=18"
}
},
+ "node_modules/complex.js": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz",
+ "integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -4632,6 +4657,12 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "license": "MIT"
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -5037,6 +5068,12 @@
"node": ">=6"
}
},
+ "node_modules/escape-latex": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
+ "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==",
+ "license": "MIT"
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -5538,7 +5575,7 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -5662,6 +5699,19 @@
"node": ">=12.20.0"
}
},
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
"node_modules/framer-motion": {
"version": "12.23.12",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.12.tgz",
@@ -6362,7 +6412,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "devOptional": true,
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6407,7 +6457,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -6453,7 +6503,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "devOptional": true,
+ "dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -6697,6 +6747,12 @@
"react": "^19.0.0"
}
},
+ "node_modules/javascript-natural-sort": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
+ "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
+ "license": "MIT"
+ },
"node_modules/jiti": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
@@ -7266,6 +7322,29 @@
"node": ">= 0.4"
}
},
+ "node_modules/mathjs": {
+ "version": "14.6.0",
+ "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.6.0.tgz",
+ "integrity": "sha512-5vI2BLB5GKQmiSK9BH6hVkZ+GgqpdnOgEfmHl7mqVmdQObLynr63KueyYYLCQMzj66q69mV2XZZGQqqxeftQbA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.26.10",
+ "complex.js": "^2.2.5",
+ "decimal.js": "^10.4.3",
+ "escape-latex": "^1.2.0",
+ "fraction.js": "^5.2.1",
+ "javascript-natural-sort": "^0.7.1",
+ "seedrandom": "^3.0.5",
+ "tiny-emitter": "^2.1.0",
+ "typed-function": "^4.2.1"
+ },
+ "bin": {
+ "mathjs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/matter-js": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/matter-js/-/matter-js-0.20.0.tgz",
@@ -7898,7 +7977,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "devOptional": true,
+ "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -8526,6 +8605,12 @@
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"license": "MIT"
},
+ "node_modules/seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
+ "license": "MIT"
+ },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -8990,11 +9075,17 @@
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
"license": "MIT"
},
+ "node_modules/tiny-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
+ "license": "MIT"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -9232,6 +9323,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/typed-function": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz",
+ "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
diff --git a/package.json b/package.json
index 18c1d275..1b97ecc4 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^3.20.0",
"@emotion/react": "^11.14.0",
"@gsap/react": "^2.1.2",
@@ -28,6 +29,7 @@
"gsap": "^3.13.0",
"lenis": "^1.3.5",
"maath": "^0.10.8",
+ "mathjs": "^14.6.0",
"matter-js": "^0.20.0",
"meshline": "^3.3.1",
"motion": "^12.23.12",
diff --git a/src/constants/Categories.js b/src/constants/Categories.js
index b54f7d3a..0d72660e 100644
--- a/src/constants/Categories.js
+++ b/src/constants/Categories.js
@@ -1,5 +1,5 @@
// Highlighted sidebar items
-export const NEW = ['Prismatic Burst', 'Gradient Blinds', 'Bubble Menu', 'Electric Border', 'Plasma', 'Prism', 'Logo Loop', 'Card Nav', 'Pill Nav', 'Target Cursor'];
+export const NEW = ['Gradual Blur', 'Prismatic Burst', 'Gradient Blinds', 'Bubble Menu', 'Electric Border', 'Plasma', 'Prism', 'Logo Loop', 'Card Nav', 'Pill Nav', 'Target Cursor'];
export const UPDATED = [];
// Used for main sidebar navigation
@@ -43,6 +43,7 @@ export const CATEGORIES = [
{
name: 'Animations',
subcategories: [
+ 'Gradual Blur',
'Animated Content',
'Fade Content',
'Electric Border',
diff --git a/src/constants/Components.js b/src/constants/Components.js
index 4a413f07..8c3c4b3d 100644
--- a/src/constants/Components.js
+++ b/src/constants/Components.js
@@ -5,6 +5,7 @@ const getStarted = {
}
const animations = {
+ 'gradual-blur': () => import("../demo/Animations/GradualBlurDemo"),
'blob-cursor': () => import("../demo/Animations/BlobCursorDemo"),
'animated-content': () => import("../demo/Animations/AnimatedContentDemo"),
'magnet': () => import("../demo/Animations/MagnetDemo"),
diff --git a/src/constants/code/Animations/gradualblurCode.js b/src/constants/code/Animations/gradualblurCode.js
new file mode 100644
index 00000000..141e8cea
--- /dev/null
+++ b/src/constants/code/Animations/gradualblurCode.js
@@ -0,0 +1,33 @@
+import { generateCliCommands } from '@/utils/utils';
+
+import code from '@content/Animations/GradualBlur/GradualBlur.jsx?raw';
+import tailwind from '@tailwind/Animations/GradualBlur/GradualBlur.jsx?raw';
+import tsCode from '@ts-default/Animations/GradualBlur/GradualBlur.tsx?raw';
+import tsTailwind from '@ts-tailwind/Animations/GradualBlur/GradualBlur.tsx?raw';
+
+export const gradualBlur = {
+ ...(generateCliCommands('Animations/GradualBlur')),
+ Installation: `npm install gradualblur mathjs`,
+ usage: `
+
+import GradualBlur from 'gradualblur'
+
+
+
+`,
+ code,
+ tailwind,
+ tsCode,
+ tsTailwind
+}
diff --git a/src/content/Animations/GradualBlur/GradualBlur.css b/src/content/Animations/GradualBlur/GradualBlur.css
new file mode 100644
index 00000000..663c9126
--- /dev/null
+++ b/src/content/Animations/GradualBlur/GradualBlur.css
@@ -0,0 +1,35 @@
+.gradual-blur-inner {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+/* Ensure backdrop-filter works with proper browser prefixes */
+.gradual-blur-inner > div {
+ -webkit-backdrop-filter: inherit;
+ backdrop-filter: inherit;
+}
+
+/* Ensure proper rendering for the blur effect */
+.gradual-blur {
+ isolation: isolate;
+}
+
+/* Fallback for browsers that don't support backdrop-filter */
+@supports not (backdrop-filter: blur(1px)) {
+ .gradual-blur-inner > div {
+ background: rgba(0, 0, 0, 0.3);
+ opacity: 0.5;
+ }
+}
+
+/* Fix for parent container positioning */
+.gradual-blur-fixed {
+ position: fixed !important;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ pointer-events: none;
+ z-index: 1000;
+}
diff --git a/src/content/Animations/GradualBlur/GradualBlur.jsx b/src/content/Animations/GradualBlur/GradualBlur.jsx
new file mode 100644
index 00000000..dfa871dd
--- /dev/null
+++ b/src/content/Animations/GradualBlur/GradualBlur.jsx
@@ -0,0 +1,387 @@
+import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react';
+import * as math from 'mathjs';
+
+// Default configuration - simplified and clean
+const DEFAULT_CONFIG = {
+ position: 'bottom',
+ strength: 2,
+ height: '6rem',
+ divCount: 5,
+ exponential: false,
+ zIndex: 1000,
+ animated: false,
+ duration: '0.3s',
+ easing: 'ease-out',
+ opacity: 1,
+ curve: 'linear',
+ responsive: false,
+ target: 'parent', // NEW: 'parent' | 'page'
+ className: '',
+ style: {}
+};
+
+// Streamlined presets - height-only approach
+const PRESETS = {
+ // Basic positions
+ top: { position: 'top', height: '6rem' },
+ bottom: { position: 'bottom', height: '6rem' },
+ left: { position: 'left', height: '6rem' },
+ right: { position: 'right', height: '6rem' },
+
+ // Intensity variations
+ subtle: { height: '4rem', strength: 1, opacity: 0.8, divCount: 3 },
+ intense: { height: '10rem', strength: 4, divCount: 8, exponential: true },
+
+ // Style variations
+ smooth: { height: '8rem', curve: 'bezier', divCount: 10 },
+ sharp: { height: '5rem', curve: 'linear', divCount: 4 },
+
+ // Common use cases
+ header: { position: 'top', height: '8rem', curve: 'ease-out' },
+ footer: { position: 'bottom', height: '8rem', curve: 'ease-out' },
+ sidebar: { position: 'left', height: '6rem', strength: 2.5 },
+
+ // Page-level presets
+ 'page-header': { position: 'top', height: '10rem', target: 'page', strength: 3 },
+ 'page-footer': { position: 'bottom', height: '10rem', target: 'page', strength: 3 }
+};
+
+// Curve functions - essential ones only
+const CURVE_FUNCTIONS = {
+ linear: (progress) => progress,
+ bezier: (progress) => progress * progress * (3 - 2 * progress),
+ 'ease-in': (progress) => progress * progress,
+ 'ease-out': (progress) => 1 - Math.pow(1 - progress, 2),
+ 'ease-in-out': (progress) =>
+ progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2
+};
+
+// Utility functions
+const mergeConfigs = (...configs) => {
+ return configs.reduce((acc, config) => ({ ...acc, ...config }), {});
+};
+
+const getGradientDirection = (position) => {
+ const directions = {
+ top: 'to top',
+ bottom: 'to bottom',
+ left: 'to left',
+ right: 'to right'
+ };
+ return directions[position] || 'to bottom';
+};
+
+const debounce = (func, wait) => {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+};
+
+// Custom hooks
+const useResponsiveHeight = (responsive, config) => {
+ const [height, setHeight] = useState(config.height);
+
+ const updateHeight = useCallback(debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newHeight = config.height;
+
+ if (screenWidth <= 480 && config.mobileHeight) {
+ newHeight = config.mobileHeight;
+ } else if (screenWidth <= 768 && config.tabletHeight) {
+ newHeight = config.tabletHeight;
+ } else if (screenWidth <= 1024 && config.desktopHeight) {
+ newHeight = config.desktopHeight;
+ }
+
+ setHeight(newHeight);
+ }, 100), [responsive, config]);
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateHeight();
+ window.addEventListener('resize', updateHeight);
+ return () => window.removeEventListener('resize', updateHeight);
+ }, [responsive, updateHeight]);
+
+ return responsive ? height : config.height;
+};
+
+const useResponsiveWidth = (responsive, config) => {
+ const [width, setWidth] = useState(config.width);
+
+ const updateWidth = useCallback(debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newWidth = config.width;
+
+ if (screenWidth <= 480 && config.mobileWidth) {
+ newWidth = config.mobileWidth;
+ } else if (screenWidth <= 768 && config.tabletWidth) {
+ newWidth = config.tabletWidth;
+ } else if (screenWidth <= 1024 && config.desktopWidth) {
+ newWidth = config.desktopWidth;
+ }
+
+ setWidth(newWidth);
+ }, 100), [responsive, config]);
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateWidth();
+ window.addEventListener('resize', updateWidth);
+ return () => window.removeEventListener('resize', updateWidth);
+ }, [responsive, updateWidth]);
+
+ return responsive ? width : config.width;
+};
+
+const useIntersectionObserver = (ref, shouldObserve = false) => {
+ const [isVisible, setIsVisible] = useState(!shouldObserve);
+
+ useEffect(() => {
+ if (!shouldObserve || !ref.current) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => setIsVisible(entry.isIntersecting),
+ { threshold: 0.1 }
+ );
+
+ observer.observe(ref.current);
+ return () => observer.disconnect();
+ }, [ref, shouldObserve]);
+
+ return isVisible;
+};
+
+// Main component
+const GradualBlur = (props) => {
+ const containerRef = useRef(null);
+ const [isHovered, setIsHovered] = useState(false);
+
+ // Merge configurations
+ const config = useMemo(() => {
+ const presetConfig = props.preset && PRESETS[props.preset] ? PRESETS[props.preset] : {};
+ return mergeConfigs(DEFAULT_CONFIG, presetConfig, props);
+ }, [props]);
+
+ // Responsive height and width
+ const responsiveHeight = useResponsiveHeight(config.responsive, config);
+ const responsiveWidth = useResponsiveWidth(config.responsive, config);
+
+ // Intersection observer for scroll animations
+ const isVisible = useIntersectionObserver(containerRef, config.animated === 'scroll');
+
+ // Memoized blur divs generation
+ const blurDivs = useMemo(() => {
+ const divs = [];
+ const increment = 100 / config.divCount;
+ const currentStrength = isHovered && config.hoverIntensity ?
+ config.strength * config.hoverIntensity : config.strength;
+
+ const curveFunc = CURVE_FUNCTIONS[config.curve] || CURVE_FUNCTIONS.linear;
+
+ for (let i = 1; i <= config.divCount; i++) {
+ let progress = i / config.divCount;
+ progress = curveFunc(progress);
+
+ // Calculate blur value
+ let blurValue;
+ if (config.exponential) {
+ blurValue = math.pow(2, progress * 4) * 0.0625 * currentStrength;
+ } else {
+ blurValue = 0.0625 * (progress * config.divCount + 1) * currentStrength;
+ }
+
+ // Calculate gradient positions
+ const p1 = math.round((increment * i - increment) * 10) / 10;
+ const p2 = math.round((increment * i) * 10) / 10;
+ const p3 = math.round((increment * i + increment) * 10) / 10;
+ const p4 = math.round((increment * i + increment * 2) * 10) / 10;
+
+ // Generate gradient
+ let gradient = `transparent ${p1}%, black ${p2}%`;
+ if (p3 <= 100) gradient += `, black ${p3}%`;
+ if (p4 <= 100) gradient += `, transparent ${p4}%`;
+
+ const direction = getGradientDirection(config.position);
+
+ const divStyle = {
+ position: 'absolute',
+ inset: '0',
+ maskImage: `linear-gradient(${direction}, ${gradient})`,
+ WebkitMaskImage: `linear-gradient(${direction}, ${gradient})`,
+ backdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
+ WebkitBackdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
+ opacity: config.opacity,
+ transition: config.animated && config.animated !== 'scroll' ?
+ `backdrop-filter ${config.duration} ${config.easing}` : undefined
+ };
+
+ divs.push(
);
+ }
+
+ return divs;
+ }, [config, isHovered]);
+
+ // Container styles with target-aware positioning
+ const containerStyle = useMemo(() => {
+ const isVertical = ['top', 'bottom'].includes(config.position);
+ const isHorizontal = ['left', 'right'].includes(config.position);
+ const isPageTarget = config.target === 'page';
+
+ const baseStyle = {
+ // Position based on target
+ position: isPageTarget ? 'fixed' : 'absolute',
+ pointerEvents: config.hoverIntensity ? 'auto' : 'none',
+ opacity: isVisible ? 1 : 0,
+ transition: config.animated ? `opacity ${config.duration} ${config.easing}` : undefined,
+ zIndex: isPageTarget ? config.zIndex + 100 : config.zIndex, // Higher z-index for page targeting
+ ...config.style
+ };
+
+ // Apply dimensions and positioning
+ if (isVertical) {
+ baseStyle.height = responsiveHeight;
+ baseStyle.width = responsiveWidth || '100%';
+ baseStyle[config.position] = 0;
+ baseStyle.left = 0;
+ baseStyle.right = 0;
+ } else if (isHorizontal) {
+ baseStyle.width = responsiveWidth || responsiveHeight; // Use width prop if provided, otherwise use height value
+ baseStyle.height = '100%';
+ baseStyle[config.position] = 0;
+ baseStyle.top = 0;
+ baseStyle.bottom = 0;
+ }
+
+ return baseStyle;
+ }, [config, responsiveHeight, responsiveWidth, isVisible]);
+
+ // Event handlers
+ const handleMouseEnter = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(true);
+ }
+ }, [config.hoverIntensity]);
+
+ const handleMouseLeave = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(false);
+ }
+ }, [config.hoverIntensity]);
+
+ // Animation complete callback
+ useEffect(() => {
+ if (isVisible && config.animated === 'scroll' && config.onAnimationComplete) {
+ const timer = setTimeout(
+ () => config.onAnimationComplete(),
+ parseFloat(config.duration) * 1000
+ );
+ return () => clearTimeout(timer);
+ }
+ }, [isVisible, config.animated, config.duration, config.onAnimationComplete]);
+
+ return (
+
+ );
+};
+
+// Factory function for creating instances with different targets
+export const createPageBlur = (props = {}) => {
+ return ;
+};
+
+export const createParentBlur = (props = {}) => {
+ return ;
+};
+
+// Export utilities
+export { PRESETS, CURVE_FUNCTIONS };
+
+export default React.memo(GradualBlur);
+
+// CSS injection function
+const injectStyles = () => {
+ if (typeof document === 'undefined') return;
+
+ const styleId = 'gradual-blur-styles';
+ if (document.getElementById(styleId)) return;
+
+ const styleElement = document.createElement('style');
+ styleElement.id = styleId;
+ styleElement.textContent = `
+ .gradual-blur {
+ pointer-events: none;
+ }
+
+ .gradual-blur-page {
+ /* Page-level blur styles */
+ }
+
+ .gradual-blur-parent {
+ /* Parent-level blur styles */
+ }
+
+ .gradual-blur-inner {
+ pointer-events: none;
+ }
+
+ /* Hover support */
+ .gradual-blur:hover .gradual-blur-inner {
+ /* Hover effects can be added here */
+ }
+
+ /* Animation support */
+ .gradual-blur {
+ transition: opacity 0.3s ease-out;
+ }
+
+ /* Responsive utilities */
+ @media (max-width: 480px) {
+ .gradual-blur-responsive {
+ /* Mobile specific styles */
+ }
+ }
+
+ @media (max-width: 768px) {
+ .gradual-blur-responsive {
+ /* Tablet specific styles */
+ }
+ }
+ `;
+
+ document.head.appendChild(styleElement);
+};
+
+// Inject styles on component mount
+if (typeof document !== 'undefined') {
+ injectStyles();
+}
diff --git a/src/demo/Animations/GradualBlurDemo.jsx b/src/demo/Animations/GradualBlurDemo.jsx
new file mode 100644
index 00000000..782012f8
--- /dev/null
+++ b/src/demo/Animations/GradualBlurDemo.jsx
@@ -0,0 +1,326 @@
+import { useState } from "react";
+import {
+ Box,
+ Heading,
+ Text,
+ VStack,
+ HStack,
+ SimpleGrid,
+ Code,
+ Image,
+ Link,
+ Button,
+} from "@chakra-ui/react";
+import {
+ CodeTab,
+ PreviewTab,
+ CliTab,
+ TabsLayout,
+} from "../../components/common/TabsLayout";
+
+import Customize from "../../components/common/Preview/Customize";
+import CodeExample from "../../components/code/CodeExample";
+import CliInstallation from "../../components/code/CliInstallation";
+import PropTable from "../../components/common/Preview/PropTable";
+import PreviewSelect from "../../components/common/Preview/PreviewSelect";
+import PreviewSlider from "../../components/common/Preview/PreviewSlider";
+
+import { gradualBlur } from "../../constants/code/Animations/gradualblurCode";
+import GradualBlur from "../../tailwind/Animations/GradualBlur/GradualBlur";
+
+const GradualBlurDemo = () => {
+ const propData = [
+ {
+ name: "position",
+ type: `"top" | "bottom | left | right"`,
+ default: `"bottom"`,
+ description: "Position of the blur overlay.",
+ },
+ {
+ name: "strength",
+ type: "number",
+ default: "2",
+ description: "Overall blur strength multiplier.",
+ },
+ {
+ name: "height",
+ type: "string",
+ default: `"7rem"`,
+ description: "Height of the blur region.",
+ },
+ {
+ name: "width",
+ type: "string",
+ default: `"100%"`,
+ description: "Width of the blur region.",
+ },
+ {
+ name: "divCount",
+ type: "number",
+ default: "5",
+ description: "Number of stacked blur layers.",
+ },
+ {
+ name: "exponential",
+ type: "boolean",
+ default: "true",
+ description: "Use exponential blur progression.",
+ },
+ {
+ name: "animated",
+ type: `"boolean" | "scroll"`,
+ default: "false",
+ description: "Enable animation or scroll-based reveal.",
+ },
+ {
+ name: "duration",
+ type: "string",
+ default: `"0.3s"`,
+ description: "Animation duration.",
+ },
+ {
+ name: "easing",
+ type: "string",
+ default: `"ease-out"`,
+ description: "Animation easing function.",
+ },
+ {
+ name: "opacity",
+ type: "number",
+ default: "1",
+ description: "Layer opacity.",
+ },
+ {
+ name: "curve",
+ type: `"linear" | "bezier" | "ease-in-out"`,
+ default: `"bezier"`,
+ description: "Controls blur progression curve.",
+ },
+ {
+ name: "responsive",
+ type: "boolean",
+ default: "false",
+ description: "Enable responsive heights.",
+ },
+ {
+ name: "preset",
+ type: `"top" | "bottom"`,
+ default: "—",
+ description: "Quickly apply a preset config.",
+ },
+ {
+ name: "gpuOptimized",
+ type: "boolean",
+ default: "false",
+ description: "Enable GPU optimization (`will-change`).",
+ },
+ {
+ name: "hoverIntensity",
+ type: "number",
+ default: "—",
+ description: "Increase blur strength on hover.",
+ },
+ {
+ name: "target",
+ type: `"parent" | "page"`,
+ default: `"parent"`,
+ description: "Position relative to parent container or entire page.",
+ },
+ {
+ name: "onAnimationComplete",
+ type: "() => void",
+ default: "—",
+ description: "Callback after animation finishes.",
+ },
+ {
+ name: "className",
+ type: "string",
+ default: "—",
+ description: "Custom CSS class.",
+ },
+ {
+ name: "style",
+ type: "React.CSSProperties",
+ default: "—",
+ description: "Inline style overrides.",
+ },
+ ];
+
+ const [blurProps, setBlurProps] = useState({
+ position: "bottom",
+ strength: 2,
+ height: "7rem",
+ divCount: 5,
+ curve: "bezier",
+ exponential: true,
+ opacity: 1,
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+ Scroll Down
+
+
+
+
+
+
+
+
+
+
+ A beautiful Flower
+
+
+ @Flower
+
+
+
+
+
+
+
+ Gradual Blur
+
+
+
+
+
+ setBlurProps((p) => ({ ...p, position: v }))}
+ />
+ setBlurProps((p) => ({ ...p, curve: v }))}
+ />
+ setBlurProps((p) => ({ ...p, exponential: v === "true" }))}
+ />
+ setBlurProps((p) => ({ ...p, strength: v }))}
+ />
+ setBlurProps((p) => ({ ...p, divCount: v }))}
+ />
+ setBlurProps((p) => ({ ...p, opacity: v }))}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default GradualBlurDemo;
diff --git a/src/tailwind/Animations/GradualBlur/GradualBlur.jsx b/src/tailwind/Animations/GradualBlur/GradualBlur.jsx
new file mode 100644
index 00000000..6d50d7ff
--- /dev/null
+++ b/src/tailwind/Animations/GradualBlur/GradualBlur.jsx
@@ -0,0 +1,395 @@
+import React, {
+ useEffect,
+ useRef,
+ useState,
+ useMemo,
+ useCallback,
+} from "react";
+import * as math from "mathjs";
+
+const DEFAULT_CONFIG = {
+ position: "bottom",
+ strength: 2,
+ height: "6rem",
+ divCount: 5,
+ exponential: true,
+ zIndex: 40,
+ animated: false,
+ duration: "0.3s",
+ easing: "ease-out",
+ opacity: 1,
+ curve: "bezier",
+ responsive: false,
+ target: "page",
+ className: "",
+ style: {},
+};
+
+// Streamlined presets - height-only approach
+const PRESETS = {
+ // Basic positions
+ top: { position: "top", height: "6rem" },
+ bottom: { position: "bottom", height: "6rem" },
+ left: { position: "left", height: "6rem" },
+ right: { position: "right", height: "6rem" },
+
+ // Intensity variations
+ subtle: { height: "4rem", strength: 1, opacity: 0.8, divCount: 3 },
+ intense: { height: "10rem", strength: 4, divCount: 8, exponential: true },
+
+ // Style variations
+ smooth: { height: "8rem", curve: "bezier", divCount: 10 },
+ sharp: { height: "5rem", curve: "linear", divCount: 4 },
+
+ // Common use cases
+ header: { position: "top", height: "8rem", curve: "ease-out" },
+ footer: { position: "bottom", height: "8rem", curve: "ease-out" },
+ sidebar: { position: "left", height: "6rem", strength: 2.5 },
+
+ // Page-level presets
+ "page-header": {
+ position: "top",
+ height: "10rem",
+ target: "page",
+ strength: 3,
+ },
+ "page-footer": {
+ position: "bottom",
+ height: "10rem",
+ target: "page",
+ strength: 3,
+ },
+};
+
+// Curve functions - essential ones only
+const CURVE_FUNCTIONS = {
+ linear: (progress) => progress,
+ bezier: (progress) => progress * progress * (3 - 2 * progress),
+ "ease-in": (progress) => progress * progress,
+ "ease-out": (progress) => 1 - Math.pow(1 - progress, 2),
+ "ease-in-out": (progress) =>
+ progress < 0.5
+ ? 2 * progress * progress
+ : 1 - Math.pow(-2 * progress + 2, 2) / 2,
+};
+
+// Utility functions
+const mergeConfigs = (...configs) => {
+ return configs.reduce((acc, config) => ({ ...acc, ...config }), {});
+};
+
+const getGradientDirection = (position) => {
+ const directions = {
+ top: "to top",
+ bottom: "to bottom",
+ left: "to left",
+ right: "to right",
+ };
+ return directions[position] || "to bottom";
+};
+
+const debounce = (func, wait) => {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+};
+
+// Custom hooks
+const useResponsiveHeight = (responsive, config) => {
+ const [height, setHeight] = useState(config.height);
+
+ const updateHeight = useCallback(
+ debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newHeight = config.height;
+
+ if (screenWidth <= 480 && config.mobileHeight) {
+ newHeight = config.mobileHeight;
+ } else if (screenWidth <= 768 && config.tabletHeight) {
+ newHeight = config.tabletHeight;
+ } else if (screenWidth <= 1024 && config.desktopHeight) {
+ newHeight = config.desktopHeight;
+ }
+
+ setHeight(newHeight);
+ }, 100),
+ [responsive, config]
+ );
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateHeight();
+ window.addEventListener("resize", updateHeight);
+ return () => window.removeEventListener("resize", updateHeight);
+ }, [responsive, updateHeight]);
+
+ return responsive ? height : config.height;
+};
+
+const useResponsiveWidth = (responsive, config) => {
+ const [width, setWidth] = useState(config.width);
+
+ const updateWidth = useCallback(
+ debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newWidth = config.width;
+
+ if (screenWidth <= 480 && config.mobileWidth) {
+ newWidth = config.mobileWidth;
+ } else if (screenWidth <= 768 && config.tabletWidth) {
+ newWidth = config.tabletWidth;
+ } else if (screenWidth <= 1024 && config.desktopWidth) {
+ newWidth = config.desktopWidth;
+ }
+
+ setWidth(newWidth);
+ }, 100),
+ [responsive, config]
+ );
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateWidth();
+ window.addEventListener("resize", updateWidth);
+ return () => window.removeEventListener("resize", updateWidth);
+ }, [responsive, updateWidth]);
+
+ return responsive ? width : config.width;
+};
+
+const useIntersectionObserver = (ref, shouldObserve = false) => {
+ const [isVisible, setIsVisible] = useState(!shouldObserve);
+
+ useEffect(() => {
+ if (!shouldObserve || !ref.current) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => setIsVisible(entry.isIntersecting),
+ { threshold: 0.1 }
+ );
+
+ observer.observe(ref.current);
+ return () => observer.disconnect();
+ }, [ref, shouldObserve]);
+
+ return isVisible;
+};
+
+// Main component
+const GradualBlur = (props) => {
+ const containerRef = useRef(null);
+ const [isHovered, setIsHovered] = useState(false);
+
+ // Merge configurations
+ const config = useMemo(() => {
+ const presetConfig =
+ props.preset && PRESETS[props.preset] ? PRESETS[props.preset] : {};
+ return mergeConfigs(DEFAULT_CONFIG, presetConfig, props);
+ }, [props]);
+
+ // Responsive height and width
+ const responsiveHeight = useResponsiveHeight(config.responsive, config);
+ const responsiveWidth = useResponsiveWidth(config.responsive, config);
+
+ // Intersection observer for scroll animations
+ const isVisible = useIntersectionObserver(
+ containerRef,
+ config.animated === "scroll"
+ );
+
+ // Memoized blur divs generation
+ const blurDivs = useMemo(() => {
+ const divs = [];
+ const increment = 100 / config.divCount;
+ const currentStrength =
+ isHovered && config.hoverIntensity
+ ? config.strength * config.hoverIntensity
+ : config.strength;
+
+ const curveFunc = CURVE_FUNCTIONS[config.curve] || CURVE_FUNCTIONS.linear;
+
+ for (let i = 1; i <= config.divCount; i++) {
+ let progress = i / config.divCount;
+ progress = curveFunc(progress);
+
+ // Calculate blur value
+ let blurValue;
+ if (config.exponential) {
+ blurValue = math.pow(2, progress * 4) * 0.0625 * currentStrength;
+ } else {
+ blurValue = 0.0625 * (progress * config.divCount + 1) * currentStrength;
+ }
+
+ // Calculate gradient positions
+ const p1 = math.round((increment * i - increment) * 10) / 10;
+ const p2 = math.round(increment * i * 10) / 10;
+ const p3 = math.round((increment * i + increment) * 10) / 10;
+ const p4 = math.round((increment * i + increment * 2) * 10) / 10;
+
+ // Generate gradient
+ let gradient = `transparent ${p1}%, black ${p2}%`;
+ if (p3 <= 100) gradient += `, black ${p3}%`;
+ if (p4 <= 100) gradient += `, transparent ${p4}%`;
+
+ const direction = getGradientDirection(config.position);
+
+ const divStyle = {
+ position: "absolute",
+ inset: "0",
+ maskImage: `linear-gradient(${direction}, ${gradient})`,
+ WebkitMaskImage: `linear-gradient(${direction}, ${gradient})`,
+ backdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
+ WebkitBackdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
+ opacity: config.opacity,
+ transition:
+ config.animated && config.animated !== "scroll"
+ ? `backdrop-filter ${config.duration} ${config.easing}`
+ : undefined,
+ };
+
+ divs.push();
+ }
+
+ return divs;
+ }, [config, isHovered]);
+
+ // Container styles with target-aware positioning
+ const containerStyle = useMemo(() => {
+ const isVertical = ["top", "bottom"].includes(config.position);
+ const isHorizontal = ["left", "right"].includes(config.position);
+ const isPageTarget = config.target === "page";
+
+ const baseStyle = {
+ // Position based on target
+ position: isPageTarget ? "fixed" : "absolute",
+ pointerEvents: config.hoverIntensity ? "auto" : "none",
+ opacity: isVisible ? 1 : 0,
+ transition: config.animated
+ ? `opacity ${config.duration} ${config.easing}`
+ : undefined,
+ zIndex: isPageTarget ? config.zIndex + 100 : config.zIndex,
+ ...config.style,
+ };
+
+ // Apply dimensions and positioning
+ if (isVertical) {
+ baseStyle.height = responsiveHeight;
+ baseStyle.width = responsiveWidth || "100%";
+ baseStyle[config.position] = 0;
+ baseStyle.left = 0;
+ baseStyle.right = 0;
+ } else if (isHorizontal) {
+ baseStyle.width = responsiveWidth || responsiveHeight;
+ baseStyle.height = "100%";
+ baseStyle[config.position] = 0;
+ baseStyle.top = 0;
+ baseStyle.bottom = 0;
+ }
+
+ return baseStyle;
+ }, [config, responsiveHeight, responsiveWidth, isVisible]);
+
+ // Event handlers
+ const handleMouseEnter = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(true);
+ }
+ }, [config.hoverIntensity]);
+
+ const handleMouseLeave = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(false);
+ }
+ }, [config.hoverIntensity]);
+
+ // Animation complete callback
+ useEffect(() => {
+ if (
+ isVisible &&
+ config.animated === "scroll" &&
+ config.onAnimationComplete
+ ) {
+ const timer = setTimeout(
+ () => config.onAnimationComplete(),
+ parseFloat(config.duration) * 1000
+ );
+ return () => clearTimeout(timer);
+ }
+ }, [isVisible, config.animated, config.duration, config.onAnimationComplete]);
+
+ return (
+
+ );
+};
+
+// Factory function for creating instances with different targets
+export const createPageBlur = (props = {}) => {
+ return ;
+};
+
+export const createParentBlur = (props = {}) => {
+ return ;
+};
+
+// Export utilities
+export { PRESETS, CURVE_FUNCTIONS };
+
+export default React.memo(GradualBlur);
diff --git a/src/ts-default/Animations/GradualBlur/GradualBlur.css b/src/ts-default/Animations/GradualBlur/GradualBlur.css
new file mode 100644
index 00000000..d57b0bdc
--- /dev/null
+++ b/src/ts-default/Animations/GradualBlur/GradualBlur.css
@@ -0,0 +1,20 @@
+/* GradualBlur.css */
+
+/* Container */
+.gradual-blur {
+ position: relative;
+ display: flex;
+ overflow: hidden;
+ pointer-events: none; /* so it doesn’t block interactions */
+}
+
+/* Common gradient layers */
+.gradual-blur > div {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ transition: opacity 0.3s ease;
+ will-change: opacity;
+}
diff --git a/src/ts-default/Animations/GradualBlur/GradualBlur.tsx b/src/ts-default/Animations/GradualBlur/GradualBlur.tsx
new file mode 100644
index 00000000..a6846e0e
--- /dev/null
+++ b/src/ts-default/Animations/GradualBlur/GradualBlur.tsx
@@ -0,0 +1,416 @@
+import React, {
+ CSSProperties,
+ useEffect,
+ useRef,
+ useState,
+ useMemo,
+ useCallback,
+ PropsWithChildren,
+} from "react";
+import * as math from "mathjs";
+import "./Gradualblur.css";
+
+type GradualBlurProps = {
+ position?: "top" | "bottom" | "left" | "right";
+ strength?: number;
+ height?: string;
+ width?: string;
+ divCount?: number;
+ exponential?: boolean;
+ zIndex?: number;
+
+ animated?: boolean | "scroll";
+ duration?: string;
+ easing?: string;
+
+ opacity?: number;
+ curve?: "linear" | "bezier" | "ease-in" | "ease-out" | "ease-in-out";
+
+ responsive?: boolean;
+ mobileHeight?: string;
+ tabletHeight?: string;
+ desktopHeight?: string;
+ mobileWidth?: string;
+ tabletWidth?: string;
+ desktopWidth?: string;
+
+ preset?: "top" | "bottom" | "left" | "right" | "subtle" | "intense" | "smooth" | "sharp" | "header" | "footer" | "sidebar" | "page-header" | "page-footer";
+ gpuOptimized?: boolean;
+ hoverIntensity?: number;
+ target?: "parent" | "page";
+
+ onAnimationComplete?: () => void;
+ className?: string;
+ style?: CSSProperties;
+};
+
+
+const DEFAULT_CONFIG: Partial = {
+ position: 'bottom',
+ strength: 2,
+ height: '6rem',
+ divCount: 5,
+ exponential: true,
+ zIndex: 40,
+ animated: false,
+ duration: '0.3s',
+ easing: 'ease-out',
+ opacity: 1,
+ curve: 'bezier',
+ responsive: false,
+ target: 'page',
+ className: '',
+ style: {}
+};
+
+
+const PRESETS: Record> = {
+
+ top: { position: 'top', height: '6rem' },
+ bottom: { position: 'bottom', height: '6rem' },
+ left: { position: 'left', height: '6rem' },
+ right: { position: 'right', height: '6rem' },
+
+
+ subtle: { height: '4rem', strength: 1, opacity: 0.8, divCount: 3 },
+ intense: { height: '10rem', strength: 4, divCount: 8, exponential: true },
+
+
+ smooth: { height: '8rem', curve: 'bezier', divCount: 10 },
+ sharp: { height: '5rem', curve: 'linear', divCount: 4 },
+
+
+ header: { position: 'top', height: '8rem', curve: 'ease-out' },
+ footer: { position: 'bottom', height: '8rem', curve: 'ease-out' },
+ sidebar: { position: 'left', height: '6rem', strength: 2.5 },
+
+
+ 'page-header': { position: 'top', height: '10rem', target: 'page', strength: 3 },
+ 'page-footer': { position: 'bottom', height: '10rem', target: 'page', strength: 3 }
+};
+
+const CURVE_FUNCTIONS: Record number> = {
+ linear: (progress) => progress,
+ bezier: (progress) => progress * progress * (3 - 2 * progress),
+ 'ease-in': (progress) => progress * progress,
+ 'ease-out': (progress) => 1 - Math.pow(1 - progress, 2),
+ 'ease-in-out': (progress) =>
+ progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2
+};
+
+const mergeConfigs = (...configs: Partial[]): Partial => {
+ return configs.reduce((acc, config) => ({ ...acc, ...config }), {});
+};
+
+const getGradientDirection = (position: string): string => {
+ const directions: Record = {
+ top: 'to top',
+ bottom: 'to bottom',
+ left: 'to left',
+ right: 'to right'
+ };
+ return directions[position] || 'to bottom';
+};
+
+const debounce = void>(func: T, wait: number): (...args: Parameters) => void => {
+ let timeout: NodeJS.Timeout;
+ return function executedFunction(...args: Parameters) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+};
+
+
+const useResponsiveHeight = (responsive: boolean = false, config: Partial) => {
+ const [height, setHeight] = useState(config.height);
+
+ const updateHeight = useCallback(debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newHeight = config.height;
+
+ if (screenWidth <= 480 && config.mobileHeight) {
+ newHeight = config.mobileHeight;
+ } else if (screenWidth <= 768 && config.tabletHeight) {
+ newHeight = config.tabletHeight;
+ } else if (screenWidth <= 1024 && config.desktopHeight) {
+ newHeight = config.desktopHeight;
+ }
+
+ setHeight(newHeight);
+ }, 100), [responsive, config]);
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateHeight();
+ window.addEventListener('resize', updateHeight);
+ return () => window.removeEventListener('resize', updateHeight);
+ }, [responsive, updateHeight]);
+
+ return responsive ? height : config.height;
+};
+
+const useResponsiveWidth = (responsive: boolean = false, config: Partial) => {
+ const [width, setWidth] = useState(config.width);
+
+ const updateWidth = useCallback(debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newWidth = config.width;
+
+ if (screenWidth <= 480 && config.mobileWidth) {
+ newWidth = config.mobileWidth;
+ } else if (screenWidth <= 768 && config.tabletWidth) {
+ newWidth = config.tabletWidth;
+ } else if (screenWidth <= 1024 && config.desktopWidth) {
+ newWidth = config.desktopWidth;
+ }
+
+ setWidth(newWidth);
+ }, 100), [responsive, config]);
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateWidth();
+ window.addEventListener('resize', updateWidth);
+ return () => window.removeEventListener('resize', updateWidth);
+ }, [responsive, updateWidth]);
+
+ return responsive ? width : config.width;
+};
+
+const useIntersectionObserver = (ref: React.RefObject, shouldObserve: boolean = false) => {
+ const [isVisible, setIsVisible] = useState(!shouldObserve);
+
+ useEffect(() => {
+ if (!shouldObserve || !ref.current) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => setIsVisible(entry.isIntersecting),
+ { threshold: 0.1 }
+ );
+
+ observer.observe(ref.current);
+ return () => observer.disconnect();
+ }, [ref, shouldObserve]);
+
+ return isVisible;
+};
+
+const GradualBlur: React.FC> = (props) => {
+ const containerRef = useRef(null);
+ const [isHovered, setIsHovered] = useState(false);
+
+
+ const config = useMemo(() => {
+ const presetConfig = props.preset && PRESETS[props.preset] ? PRESETS[props.preset] : {};
+ return mergeConfigs(DEFAULT_CONFIG, presetConfig, props) as Required;
+ }, [props]);
+
+
+ const responsiveHeight = useResponsiveHeight(config.responsive, config);
+ const responsiveWidth = useResponsiveWidth(config.responsive, config);
+
+ const isVisible = useIntersectionObserver(containerRef, config.animated === 'scroll');
+
+
+ const blurDivs = useMemo(() => {
+ const divs: React.ReactNode[] = [];
+ const increment = 100 / config.divCount;
+ const currentStrength = isHovered && config.hoverIntensity ?
+ config.strength * config.hoverIntensity : config.strength;
+
+ const curveFunc = CURVE_FUNCTIONS[config.curve] || CURVE_FUNCTIONS.linear;
+
+ for (let i = 1; i <= config.divCount; i++) {
+ let progress = i / config.divCount;
+ progress = curveFunc(progress);
+
+ // Calculate blur value
+ let blurValue: number;
+ if (config.exponential) {
+ blurValue = Number(math.pow(2, progress * 4)) * 0.0625 * currentStrength;
+ } else {
+ blurValue = 0.0625 * (progress * config.divCount + 1) * currentStrength;
+ }
+
+ // Calculate gradient positions
+ const p1 = math.round((increment * i - increment) * 10) / 10;
+ const p2 = math.round((increment * i) * 10) / 10;
+ const p3 = math.round((increment * i + increment) * 10) / 10;
+ const p4 = math.round((increment * i + increment * 2) * 10) / 10;
+
+ // Generate gradient
+ let gradient = `transparent ${p1}%, black ${p2}%`;
+ if (p3 <= 100) gradient += `, black ${p3}%`;
+ if (p4 <= 100) gradient += `, transparent ${p4}%`;
+
+ const direction = getGradientDirection(config.position);
+
+ const divStyle: CSSProperties = {
+ position: 'absolute',
+ inset: '0',
+ maskImage: `linear-gradient(${direction}, ${gradient})`,
+ WebkitMaskImage: `linear-gradient(${direction}, ${gradient})`,
+ backdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
+ WebkitBackdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
+ opacity: config.opacity,
+ transition: config.animated && config.animated !== 'scroll' ?
+ `backdrop-filter ${config.duration} ${config.easing}` : undefined
+ };
+
+ divs.push();
+ }
+
+ return divs;
+ }, [config, isHovered]);
+
+ const containerStyle: CSSProperties = useMemo(() => {
+ const isVertical = ['top', 'bottom'].includes(config.position);
+ const isHorizontal = ['left', 'right'].includes(config.position);
+ const isPageTarget = config.target === 'page';
+
+ const baseStyle: CSSProperties = {
+
+ position: isPageTarget ? 'fixed' : 'absolute',
+ pointerEvents: config.hoverIntensity ? 'auto' : 'none',
+ opacity: isVisible ? 1 : 0,
+ transition: config.animated ? `opacity ${config.duration} ${config.easing}` : undefined,
+ zIndex: isPageTarget ? config.zIndex + 100 : config.zIndex,
+ ...config.style
+ };
+
+
+ if (isVertical) {
+ baseStyle.height = responsiveHeight;
+ baseStyle.width = responsiveWidth || '100%';
+ baseStyle[config.position] = 0;
+ baseStyle.left = 0;
+ baseStyle.right = 0;
+ } else if (isHorizontal) {
+ baseStyle.width = responsiveWidth || responsiveHeight;
+ baseStyle.height = '100%';
+ baseStyle[config.position] = 0;
+ baseStyle.top = 0;
+ baseStyle.bottom = 0;
+ }
+
+ return baseStyle;
+ }, [config, responsiveHeight, responsiveWidth, isVisible]);
+
+
+ const handleMouseEnter = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(true);
+ }
+ }, [config.hoverIntensity]);
+
+ const handleMouseLeave = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(false);
+ }
+ }, [config.hoverIntensity]);
+
+
+ useEffect(() => {
+ if (isVisible && config.animated === 'scroll' && config.onAnimationComplete) {
+ const timer = setTimeout(
+ () => config.onAnimationComplete!(),
+ parseFloat(config.duration) * 1000
+ );
+ return () => clearTimeout(timer);
+ }
+ }, [isVisible, config.animated, config.duration, config.onAnimationComplete]);
+
+ return (
+
+ );
+};
+
+
+export const createPageBlur = (props: Partial = {}) => {
+ return ;
+};
+
+export const createParentBlur = (props: Partial = {}) => {
+ return ;
+};
+
+
+export { PRESETS, CURVE_FUNCTIONS };
+
+export default React.memo(GradualBlur);
+
+
+const injectStyles = () => {
+ if (typeof document === 'undefined') return;
+
+ const styleId = 'gradual-blur-styles';
+ if (document.getElementById(styleId)) return;
+
+ const styleElement = document.createElement('style');
+ styleElement.id = styleId;
+ styleElement.textContent = `
+ .gradual-blur {
+ pointer-events: none;
+ }
+
+ .gradual-blur-page {
+ }
+
+ .gradual-blur-parent {
+ }
+
+ .gradual-blur-inner {
+ pointer-events: none;
+ }
+
+ .gradual-blur:hover .gradual-blur-inner {
+ }
+
+ .gradual-blur {
+ transition: opacity 0.3s ease-out;
+ }
+
+ @media (max-width: 480px) {
+ .gradual-blur-responsive {
+ }
+ }
+
+ @media (max-width: 768px) {
+ .gradual-blur-responsive {
+ }
+ }
+ `;
+
+ document.head.appendChild(styleElement);
+};
+
+if (typeof document !== 'undefined') {
+ injectStyles();
+}
diff --git a/src/ts-tailwind/Animations/GradualBlur/GradualBlur.tsx b/src/ts-tailwind/Animations/GradualBlur/GradualBlur.tsx
new file mode 100644
index 00000000..0dd9676b
--- /dev/null
+++ b/src/ts-tailwind/Animations/GradualBlur/GradualBlur.tsx
@@ -0,0 +1,425 @@
+import React, {
+ CSSProperties,
+ useEffect,
+ useRef,
+ useState,
+ useMemo,
+ useCallback,
+ PropsWithChildren,
+} from "react";
+import * as math from "mathjs";
+
+type GradualBlurProps = PropsWithChildren<{
+
+ position?: "top" | "bottom" | "left" | "right";
+ strength?: number;
+ height?: string;
+ width?: string;
+ divCount?: number;
+ exponential?: boolean;
+ zIndex?: number;
+
+
+ animated?: boolean | "scroll";
+ duration?: string;
+ easing?: string;
+
+
+ opacity?: number;
+ curve?: "linear" | "bezier" | "ease-in" | "ease-out" | "ease-in-out";
+
+ responsive?: boolean;
+ mobileHeight?: string;
+ tabletHeight?: string;
+ desktopHeight?: string;
+ mobileWidth?: string;
+ tabletWidth?: string;
+ desktopWidth?: string;
+
+
+ preset?: "top" | "bottom" | "left" | "right" | "subtle" | "intense" | "smooth" | "sharp" | "header" | "footer" | "sidebar" | "page-header" | "page-footer";
+ gpuOptimized?: boolean;
+ hoverIntensity?: number;
+ target?: "parent" | "page";
+
+ onAnimationComplete?: () => void;
+ className?: string;
+ style?: CSSProperties;
+}>;
+
+
+const DEFAULT_CONFIG: Partial = {
+ position: 'bottom',
+ strength: 2,
+ height: '6rem',
+ divCount: 5,
+ exponential: true,
+ zIndex: 40,
+ animated: false,
+ duration: '0.3s',
+ easing: 'ease-out',
+ opacity: 1,
+ curve: 'bezier',
+ responsive: false,
+ target: 'page',
+ className: '',
+ style: {}
+};
+
+
+const PRESETS: Record> = {
+
+ top: { position: 'top', height: '6rem' },
+ bottom: { position: 'bottom', height: '6rem' },
+ left: { position: 'left', height: '6rem' },
+ right: { position: 'right', height: '6rem' },
+
+
+ subtle: { height: '4rem', strength: 1, opacity: 0.8, divCount: 3 },
+ intense: { height: '10rem', strength: 4, divCount: 8, exponential: true },
+
+ smooth: { height: '8rem', curve: 'bezier', divCount: 10 },
+ sharp: { height: '5rem', curve: 'linear', divCount: 4 },
+
+
+ header: { position: 'top', height: '8rem', curve: 'ease-out' },
+ footer: { position: 'bottom', height: '8rem', curve: 'ease-out' },
+ sidebar: { position: 'left', height: '6rem', strength: 2.5 },
+
+
+ 'page-header': { position: 'top', height: '10rem', target: 'page', strength: 3 },
+ 'page-footer': { position: 'bottom', height: '10rem', target: 'page', strength: 3 }
+};
+
+
+const CURVE_FUNCTIONS: Record number> = {
+ linear: (progress) => progress,
+ bezier: (progress) => progress * progress * (3 - 2 * progress),
+ 'ease-in': (progress) => progress * progress,
+ 'ease-out': (progress) => 1 - Math.pow(1 - progress, 2),
+ 'ease-in-out': (progress) =>
+ progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2
+};
+
+
+const mergeConfigs = (...configs: Partial[]): Partial => {
+ return configs.reduce((acc, config) => ({ ...acc, ...config }), {});
+};
+
+const getGradientDirection = (position: string): string => {
+ const directions: Record = {
+ top: 'to top',
+ bottom: 'to bottom',
+ left: 'to left',
+ right: 'to right'
+ };
+ return directions[position] || 'to bottom';
+};
+
+const debounce = void>(func: T, wait: number): (...args: Parameters) => void => {
+ let timeout: NodeJS.Timeout;
+ return function executedFunction(...args: Parameters) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+};
+
+
+const useResponsiveHeight = (responsive: boolean = false, config: Partial) => {
+ const [height, setHeight] = useState(config.height);
+
+ const updateHeight = useCallback(debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newHeight = config.height;
+
+ if (screenWidth <= 480 && config.mobileHeight) {
+ newHeight = config.mobileHeight;
+ } else if (screenWidth <= 768 && config.tabletHeight) {
+ newHeight = config.tabletHeight;
+ } else if (screenWidth <= 1024 && config.desktopHeight) {
+ newHeight = config.desktopHeight;
+ }
+
+ setHeight(newHeight);
+ }, 100), [responsive, config]);
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateHeight();
+ window.addEventListener('resize', updateHeight);
+ return () => window.removeEventListener('resize', updateHeight);
+ }, [responsive, updateHeight]);
+
+ return responsive ? height : config.height;
+};
+
+const useResponsiveWidth = (responsive: boolean = false, config: Partial) => {
+ const [width, setWidth] = useState(config.width);
+
+ const updateWidth = useCallback(debounce(() => {
+ if (!responsive) return;
+
+ const screenWidth = window.innerWidth;
+ let newWidth = config.width;
+
+ if (screenWidth <= 480 && config.mobileWidth) {
+ newWidth = config.mobileWidth;
+ } else if (screenWidth <= 768 && config.tabletWidth) {
+ newWidth = config.tabletWidth;
+ } else if (screenWidth <= 1024 && config.desktopWidth) {
+ newWidth = config.desktopWidth;
+ }
+
+ setWidth(newWidth);
+ }, 100), [responsive, config]);
+
+ useEffect(() => {
+ if (!responsive) return;
+
+ updateWidth();
+ window.addEventListener('resize', updateWidth);
+ return () => window.removeEventListener('resize', updateWidth);
+ }, [responsive, updateWidth]);
+
+ return responsive ? width : config.width;
+};
+
+const useIntersectionObserver = (ref: React.RefObject, shouldObserve: boolean = false) => {
+ const [isVisible, setIsVisible] = useState(!shouldObserve);
+
+ useEffect(() => {
+ if (!shouldObserve || !ref.current) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => setIsVisible(entry.isIntersecting),
+ { threshold: 0.1 }
+ );
+
+ observer.observe(ref.current);
+ return () => observer.disconnect();
+ }, [ref, shouldObserve]);
+
+ return isVisible;
+};
+
+const GradualBlur: React.FC = (props) => {
+ const containerRef = useRef(null) as React.RefObject;
+ const [isHovered, setIsHovered] = useState(false);
+
+
+ const config = useMemo(() => {
+ const presetConfig = props.preset && PRESETS[props.preset] ? PRESETS[props.preset] : {};
+ return mergeConfigs(DEFAULT_CONFIG, presetConfig, props) as Required;
+ }, [props]);
+
+
+ const responsiveHeight = useResponsiveHeight(config.responsive, config);
+ const responsiveWidth = useResponsiveWidth(config.responsive, config);
+
+
+ const isVisible = useIntersectionObserver(containerRef, config.animated === 'scroll');
+
+
+ const blurDivs = useMemo(() => {
+ const divs: React.ReactNode[] = [];
+ const increment = 100 / config.divCount;
+ const currentStrength = isHovered && config.hoverIntensity ?
+ config.strength * config.hoverIntensity : config.strength;
+
+ const curveFunc = CURVE_FUNCTIONS[config.curve] || CURVE_FUNCTIONS.linear;
+
+ for (let i = 1; i <= config.divCount; i++) {
+ let progress = i / config.divCount;
+ progress = curveFunc(progress);
+
+
+ let blurValue: number;
+ if (config.exponential) {
+ blurValue = Number(math.pow(2, progress * 4)) * 0.0625 * currentStrength;
+ } else {
+ blurValue = 0.0625 * (progress * config.divCount + 1) * currentStrength;
+ }
+
+
+ const p1 = math.round((increment * i - increment) * 10) / 10;
+ const p2 = math.round((increment * i) * 10) / 10;
+ const p3 = math.round((increment * i + increment) * 10) / 10;
+ const p4 = math.round((increment * i + increment * 2) * 10) / 10;
+
+
+ let gradient = `transparent ${p1}%, black ${p2}%`;
+ if (p3 <= 100) gradient += `, black ${p3}%`;
+ if (p4 <= 100) gradient += `, transparent ${p4}%`;
+
+ const direction = getGradientDirection(config.position);
+
+ const divStyle: CSSProperties = {
+ maskImage: `linear-gradient(${direction}, ${gradient})`,
+ WebkitMaskImage: `linear-gradient(${direction}, ${gradient})`,
+ backdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
+ opacity: config.opacity,
+ transition: config.animated && config.animated !== 'scroll' ?
+ `backdrop-filter ${config.duration} ${config.easing}` : undefined
+ };
+
+ divs.push(
+
+ );
+ }
+
+ return divs;
+ }, [config, isHovered]);
+
+
+ const containerStyle: CSSProperties = useMemo(() => {
+ const isVertical = ['top', 'bottom'].includes(config.position);
+ const isHorizontal = ['left', 'right'].includes(config.position);
+ const isPageTarget = config.target === 'page';
+
+ const baseStyle: CSSProperties = {
+
+ position: isPageTarget ? 'fixed' : 'absolute',
+ pointerEvents: config.hoverIntensity ? 'auto' : 'none',
+ opacity: isVisible ? 1 : 0,
+ transition: config.animated ? `opacity ${config.duration} ${config.easing}` : undefined,
+ zIndex: isPageTarget ? config.zIndex + 100 : config.zIndex,
+ ...config.style
+ };
+
+
+ if (isVertical) {
+ baseStyle.height = responsiveHeight;
+ baseStyle.width = responsiveWidth || '100%';
+ baseStyle[config.position] = 0;
+ baseStyle.left = 0;
+ baseStyle.right = 0;
+ } else if (isHorizontal) {
+ baseStyle.width = responsiveWidth || responsiveHeight;
+ baseStyle.height = '100%';
+ baseStyle[config.position] = 0;
+ baseStyle.top = 0;
+ baseStyle.bottom = 0;
+ }
+
+ return baseStyle;
+ }, [config, responsiveHeight, responsiveWidth, isVisible]);
+
+
+ const handleMouseEnter = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(true);
+ }
+ }, [config.hoverIntensity]);
+
+ const handleMouseLeave = useCallback(() => {
+ if (config.hoverIntensity) {
+ setIsHovered(false);
+ }
+ }, [config.hoverIntensity]);
+
+
+ useEffect(() => {
+ if (isVisible && config.animated === 'scroll' && config.onAnimationComplete) {
+ const timer = setTimeout(
+ () => config.onAnimationComplete!(),
+ parseFloat(config.duration) * 1000
+ );
+ return () => clearTimeout(timer);
+ }
+ }, [isVisible, config.animated, config.duration, config.onAnimationComplete]);
+
+ return (
+
+
{blurDivs}
+ {props.children &&
{props.children}
}
+
+ );
+};
+
+
+export const createPageBlur = (props: Partial = {}) => {
+ return ;
+};
+
+export const createParentBlur = (props: Partial = {}) => {
+ return ;
+};
+
+
+export { PRESETS, CURVE_FUNCTIONS };
+
+export default React.memo(GradualBlur);
+
+
+const injectStyles = () => {
+ if (typeof document === 'undefined') return;
+
+ const styleId = 'gradual-blur-styles';
+ if (document.getElementById(styleId)) return;
+
+ const styleElement = document.createElement('style');
+ styleElement.id = styleId;
+ styleElement.textContent = `
+ .gradual-blur {
+ pointer-events: none;
+ }
+
+ .gradual-blur-page {
+ /* Page-level blur styles */
+ }
+
+ .gradual-blur-parent {
+ /* Parent-level blur styles */
+ }
+
+ .gradual-blur-inner {
+ pointer-events: none;
+ }
+
+ /* Hover support */
+ .gradual-blur:hover .gradual-blur-inner {
+ /* Hover effects can be added here */
+ }
+
+ /* Animation support */
+ .gradual-blur {
+ transition: opacity 0.3s ease-out;
+ }
+
+ /* Responsive utilities */
+ @media (max-width: 480px) {
+ .gradual-blur-responsive {
+ /* Mobile specific styles */
+ }
+ }
+
+ @media (max-width: 768px) {
+ .gradual-blur-responsive {
+ /* Tablet specific styles */
+ }
+ }
+ `;
+
+ document.head.appendChild(styleElement);
+};
+
+if (typeof document !== 'undefined') {
+ injectStyles();
+}