aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
267 lines (264 loc) • 10.4 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { forwardRef, useRef, useState, useEffect, useCallback } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
import { ZLayer } from '../../core/zspace.js';
import { useReducedMotion } from '../../hooks/useReducedMotion.js';
import { createGlassStyle } from '../../core/mixins/glassMixins.js';
import styles from './GlassDynamicAtmosphere.module.css.js';
const clamp01 = value => Math.min(1, Math.max(0, value));
const applyAlpha = (color, alpha) => {
if (!color) return `rgba(0, 0, 0, ${clamp01(alpha)})`;
if (color.startsWith('#')) {
const normalized = color.replace('#', '');
if (normalized.length === 3) {
const r = parseInt(normalized[0] + normalized[0], 16);
const g = parseInt(normalized[1] + normalized[1], 16);
const b = parseInt(normalized[2] + normalized[2], 16);
return `rgba(${r}, ${g}, ${b}, ${clamp01(alpha)})`;
}
if (normalized.length === 6) {
const r = parseInt(normalized.slice(0, 2), 16);
const g = parseInt(normalized.slice(2, 4), 16);
const b = parseInt(normalized.slice(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${clamp01(alpha)})`;
}
}
return color;
};
/**
* DynamicAtmosphere Component
*
* A component that creates dynamic atmospheric background effects.
*/
const DynamicAtmosphere = /*#__PURE__*/forwardRef((props, ref) => {
const {
type = 'subtle',
primaryColor = '#6366F1',
// Primary (purple)
secondaryColor = 'var(--glass-color-primary)',
// Secondary (blue)
accentColor = 'var(--glass-color-success)',
// Accent (green)
intensity = 0.5,
speed = 1,
interactionMode = 'none',
interactionSensitivity = 0.5,
fullSize = true,
width = '100%',
height = '100%',
style,
className,
zIndex = ZLayer.Background,
position = 'absolute',
respectReducedMotion = true,
particleCount = 20,
blur = false,
blurStrength = 5,
noise = false,
...rest
} = props;
const prefersReducedMotion = useReducedMotion();
const shouldReduceMotion = respectReducedMotion && prefersReducedMotion;
const containerRef = useRef(null);
const [transform, setTransform] = useState('');
// Compute and inject CSS variables for backgrounds to avoid inline glass literals
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const i = intensity;
const pc = primaryColor;
const sc = secondaryColor;
const ac = accentColor;
let bg = '';
if (type === 'subtle') {
bg = `radial-gradient(circle at 50% 50%, ${applyAlpha(pc, i * 0.4)}, transparent 70%)`;
} else if (type === 'nebula') {
bg = `radial-gradient(circle at 30% 50%, ${applyAlpha(pc, i * 0.6)}, transparent 50%), radial-gradient(circle at 70% 50%, ${applyAlpha(sc, i * 0.6)}, transparent 50%)`;
} else if (type === 'waves') {
bg = `linear-gradient(135deg, ${applyAlpha(pc, i * 0.5)}, ${applyAlpha(sc, i * 0.5)}, ${applyAlpha(ac, i * 0.5)}, ${applyAlpha(pc, i * 0.5)})`;
} else if (type === 'gradient') {
bg = `linear-gradient(-45deg, ${applyAlpha(pc, i * 0.4)}, ${applyAlpha(sc, i * 0.4)}, ${applyAlpha(ac, i * 0.4)}, ${applyAlpha(pc, i * 0.4)})`;
}
if (bg) el.style.setProperty('--atmosphere-bg', bg);
if (type === 'aurora') {
const aur = `linear-gradient(90deg, ${applyAlpha(pc, 0)}, ${applyAlpha(pc, i * 0.6)}, ${applyAlpha(sc, i * 0.6)}, ${applyAlpha(ac, i * 0.6)}, ${applyAlpha(pc, 0)})`;
el.style.setProperty('--atmosphere-aurora-bg', aur);
}
}, [type, intensity, primaryColor, secondaryColor, accentColor]);
// Convert width and height to string
const widthValue = typeof width === 'number' ? `${width}px` : width;
const heightValue = typeof height === 'number' ? `${height}px` : height;
const safeSpeed = Math.max(speed, 0.1);
const baseAnimationDuration = `${30 / safeSpeed}s`;
const particleAnimationDuration = `${15 / safeSpeed}s`;
const containerStyle = {
position,
width: fullSize ? '100%' : widthValue,
height: fullSize ? '100%' : heightValue,
zIndex,
overflow: 'hidden',
pointerEvents: 'none',
...style
};
if (!fullSize) {
containerStyle.width = widthValue;
containerStyle.height = heightValue;
}
if (position === 'absolute' || position === 'fixed') {
containerStyle.top = 0;
containerStyle.left = 0;
containerStyle.right = 0;
containerStyle.bottom = 0;
}
if (blur) {
Object.assign(containerStyle, createGlassStyle({
intent: 'neutral',
elevation: 'level2'
}));
containerStyle.backdropFilter = `blur(${blurStrength}px)`;
containerStyle.WebkitBackdropFilter = `blur(${blurStrength}px)`;
}
const effectStyle = {
transform
};
const intensityValue = clamp01(intensity);
const typeClassMap = {
subtle: styles.typeSubtle,
nebula: styles.typeNebula,
aurora: styles.typeAurora,
particles: styles.typeDefault,
waves: styles.typeWaves,
gradient: styles.typeGradient,
ambient: styles.typeAmbient,
custom: styles.typeDefault
};
const animationClassMap = {
subtle: styles.animateSubtle,
nebula: styles.animateNebula,
aurora: styles.animateAurora,
waves: styles.animateWaves,
gradient: styles.animateGradient
};
if (!shouldReduceMotion) {
effectStyle['--atmosphere-animation-duration'] = baseAnimationDuration;
}
switch (type) {
case 'subtle':
effectStyle.opacity = intensityValue * 0.7 + 0.3;
break;
case 'nebula':
effectStyle.opacity = intensityValue * 0.8 + 0.2;
break;
case 'aurora':
effectStyle['--atmosphere-aurora-opacity'] = intensityValue * 0.8 + 0.2;
break;
case 'waves':
effectStyle.opacity = intensityValue * 0.7 + 0.3;
break;
case 'gradient':
effectStyle.opacity = intensityValue * 0.7 + 0.3;
break;
case 'ambient':
effectStyle.opacity = intensityValue * 0.6 + 0.4;
effectStyle.background = `radial-gradient(circle at 20% 30%, ${applyAlpha(primaryColor, intensityValue * 0.5)}, transparent 50%), radial-gradient(circle at 80% 70%, ${applyAlpha(secondaryColor, intensityValue * 0.5)}, transparent 50%), radial-gradient(circle at 50% 50%, ${applyAlpha(accentColor, intensityValue * 0.4)}, transparent 70%)`;
break;
default:
effectStyle.opacity = intensityValue * 0.5 + 0.5;
effectStyle.backgroundColor = applyAlpha(primaryColor, intensityValue * 0.3);
break;
}
const effectClasses = cn(styles.effect, typeClassMap[type] ?? styles.typeDefault, noise && styles.noise, !shouldReduceMotion && animationClassMap[type], shouldReduceMotion && styles.reduceMotion);
// Generate particles
const renderParticles = () => {
if (type !== 'particles') return null;
return jsx("div", {
className: styles.particleContainer,
children: Array.from({
length: particleCount
}).map((_, index) => {
const size = Math.random() * 8 + 2;
const positionX = Math.random() * 100;
const positionY = Math.random() * 100;
const delay = Math.random() * 5;
const particleStyle = {
backgroundColor: primaryColor,
width: `${size}px`,
height: `${size}px`,
top: `${positionY}%`,
left: `${positionX}%`
};
if (!shouldReduceMotion) {
particleStyle['--particle-animation-duration'] = particleAnimationDuration;
particleStyle['--particle-animation-delay'] = `${delay}s`;
}
return jsx("div", {
className: cn(styles.particle, !shouldReduceMotion && styles.particleAnimated, shouldReduceMotion && styles.reduceMotion),
style: particleStyle
}, `particle-${index}`);
})
});
};
// Handle mouse movement interaction
const handleMouseMove = useCallback(e => {
if (interactionMode !== 'mouse' || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
// Calculate offset based on mouse position and sensitivity
const offsetX = (x - 0.5) * interactionSensitivity * 20;
const offsetY = (y - 0.5) * interactionSensitivity * 20;
setTransform(`translate(${offsetX}px, ${offsetY}px)`);
}, [interactionMode, interactionSensitivity, shouldReduceMotion]);
// Handle scroll interaction
const handleScroll = useCallback(() => {
if (interactionMode !== 'scroll' || !containerRef.current) return;
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
// Calculate how far the element is in the viewport
const rect = containerRef.current.getBoundingClientRect();
const elementTop = rect.top + scrollY;
const elementVisible = Math.min(windowHeight, Math.max(0, scrollY + windowHeight - elementTop)) / windowHeight;
// Apply transform based on scroll position
const offsetY = (elementVisible - 0.5) * interactionSensitivity * 30;
setTransform(`translateY(${offsetY}px)`);
}, [interactionMode, interactionSensitivity, shouldReduceMotion]);
// Set up event listeners
useEffect(() => {
if (shouldReduceMotion) return;
if (interactionMode === 'mouse') {
window.addEventListener('mousemove', handleMouseMove);
} else if (interactionMode === 'scroll') {
window.addEventListener('scroll', handleScroll);
// Initial calculation
handleScroll();
}
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('scroll', handleScroll);
};
}, [interactionMode, handleMouseMove, handleScroll, shouldReduceMotion]);
const setContainerRef = node => {
if (containerRef.current !== node) {
containerRef.current = node;
}
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
ref.current = node;
}
};
return jsxs("div", {
ref: setContainerRef,
className: cn('glass-dynamic-atmosphere', styles.container, className),
style: containerStyle,
...rest,
children: [jsx("div", {
className: effectClasses,
style: effectStyle
}), renderParticles()]
});
});
DynamicAtmosphere.displayName = 'GlassDynamicAtmosphere';
export { DynamicAtmosphere, DynamicAtmosphere as default };
//# sourceMappingURL=GlassDynamicAtmosphere.js.map