UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

1,554 lines (1,512 loc) 91.5 kB
'use client'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import * as THREE from 'three'; import { Canvas, useThree, useFrame } from '@react-three/fiber'; import { AnimatePresence, motion } from 'framer-motion'; import { RotateCcw, Zap, Triangle, Pause, Play, Wind, Cloud, Leaf, Sun, Flower, Snowflake, Flame, Sparkles, Star, Palette, Waves, AlertCircle, Loader2, EyeOff, Eye, Info, Hand } from 'lucide-react'; import { OrbitControls, PerspectiveCamera } from '@react-three/drei'; import { clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; /** * AuraGlass Utility Functions * Production-ready utility functions for the AuraGlass design system */ // Class name utility function function cn(...inputs) { return twMerge(clsx(inputs)); } /** * Detects whether the current React runtime exposes the React 19+ reconciler internals. * React 18 does not define the `S` slot, which newer reconcilers rely on. */ const isReact19OrNewer = () => { const internals = // @ts-ignore - accessing React internals on purpose to detect React 19 shape React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; return Boolean(internals && typeof internals === "object" && "S" in internals); }; const assertReact19ForThree = () => { if (!isReact19OrNewer()) { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.warn("[AuraGlass] 3D glass effects require React 19+. Running under React 18 – three-based effects are disabled."); } return false; } return true; }; const shatterPresets = { gentle: { duration: 1.5, intensity: 0.5, shardCount: 8, autoReform: true, reformDelay: 2000 }, dramatic: { duration: 2.5, intensity: 1.2, shardCount: 16, autoReform: true, reformDelay: 4000 }, explosive: { duration: 3, intensity: 2, shardCount: 24, autoReform: false, reformDelay: 6000 }, subtle: { duration: 1, intensity: 0.3, shardCount: 6, autoReform: true, reformDelay: 1500 } }; let LoadedImpl$3 = null; function GlassShatterEffects(props) { const [Impl, setImpl] = useState(LoadedImpl$3); useEffect(() => { let cancelled = false; if (!assertReact19ForThree()) { return; } if (!Impl) { Promise.resolve().then(function () { return GlassShatterEffects_r3f; }).then(mod => { if (cancelled) return; const Component = mod.GlassShatterEffectsR3F || mod.default; LoadedImpl$3 = Component; setImpl(() => Component); }).catch(error => { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.error("[AuraGlass] Failed to load GlassShatterEffects R3F module", error); } }); } return () => { cancelled = true; }; }, [Impl]); if (!Impl) { const { className, children, trigger = "click", ...rest } = props; return jsx("div", { className: cn("glass-shatter-effects glass-relative glass-overflow-hidden", className), style: { position: "relative", cursor: trigger === "click" ? "pointer" : "default" }, ...rest, children: jsx("div", { className: cn("content glass-transition-opacity glass-duration-300", "glass-opacity-100"), children: children }) }); } return jsx(Impl, { ...props }); } const seasonalPresets = { gentle: { particleCount: 20, windStrength: 0.5, animationSpeed: 0.8, seasonDuration: 15000 }, lively: { particleCount: 40, windStrength: 1.2, animationSpeed: 1.2, seasonDuration: 10000 }, dramatic: { particleCount: 60, windStrength: 2.0, animationSpeed: 1.5, seasonDuration: 8000 }, subtle: { particleCount: 15, windStrength: 0.3, animationSpeed: 0.6, seasonDuration: 20000 } }; const seasonalThemes = { winter: { background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", glassColor: "var(--glass-bg-default)", accentColor: "#e3f2fd" }, spring: { background: "linear-gradient(135deg, #f093fb 0%, #f5576c 100%)", glassColor: "var(--glass-bg-disabled)", accentColor: "#fce4ec" }, summer: { background: "linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)", glassColor: "var(--glass-bg-default)", accentColor: "#fff3e0" }, autumn: { background: "linear-gradient(135deg, #fa709a 0%, #fee140 100%)", glassColor: "rgba(255, 255, 255, 0.12)", accentColor: "#efebe9" } }; let LoadedImpl$2 = null; function SeasonalParticles(props) { const [Impl, setImpl] = useState(LoadedImpl$2); useEffect(() => { let cancelled = false; if (!assertReact19ForThree()) { return; } if (!Impl) { Promise.resolve().then(function () { return SeasonalParticles_r3f; }).then(mod => { if (cancelled) return; const Component = mod.SeasonalParticlesR3F || mod.default; LoadedImpl$2 = Component; setImpl(() => Component); }).catch(error => { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.error("[AuraGlass] Failed to load SeasonalParticles R3F module", error); } }); } return () => { cancelled = true; }; }, [Impl]); if (!Impl) { const { className, children, ...rest } = props; return jsx("div", { className: cn("seasonal-particles glass-relative glass-overflow-hidden", className), ...rest, children: jsx("div", { className: cn("glass-relative glass-z-10"), children: children }) }); } return jsx(Impl, { ...props }); } const auroraPresets = { subtle: { intensity: 0.6, speed: 0.5, particleCount: 30, colorPalette: "arctic", animationMode: "flow" }, dynamic: { intensity: 1.0, speed: 1.0, particleCount: 50, colorPalette: "cosmic", animationMode: "mixed" }, intense: { intensity: 1.5, speed: 1.5, particleCount: 80, colorPalette: "sunset", animationMode: "pulse" }, serene: { intensity: 0.4, speed: 0.3, particleCount: 20, colorPalette: "ocean", animationMode: "shift" } }; const auroraThemes = { northern: { background: "linear-gradient(180deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%)", glassColor: "rgba(79, 195, 247, 0.1)", accentColor: "#4fc3f7" }, mystical: { background: "linear-gradient(180deg, #0f0f23 0%, #1a1a2e 50%, #2d1b69 100%)", glassColor: "rgba(186, 104, 200, 0.1)", accentColor: "#ba68c8" }, tropical: { background: "linear-gradient(180deg, #0c0c0c 0%, #1a1a2e 50%, #0f3460 100%)", glassColor: "rgba(0, 188, 212, 0.1)", accentColor: "#00bcd4" }, enchanted: { background: "linear-gradient(180deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)", glassColor: "rgba(255, 152, 77, 0.1)", accentColor: "#ff9843" } }; let LoadedImpl$1 = null; function AuroraPro$1(props) { const [Impl, setImpl] = useState(LoadedImpl$1); useEffect(() => { let cancelled = false; if (!assertReact19ForThree()) { return; } if (!Impl) { Promise.resolve().then(function () { return AuroraPro_r3f; }).then(mod => { if (cancelled) return; const Component = mod.AuroraPro || mod.default; LoadedImpl$1 = Component; setImpl(() => Component); }).catch(error => { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.error("[AuraGlass] Failed to load AuroraPro R3F module", error); } }); } return () => { cancelled = true; }; }, [Impl]); if (!Impl) { const { className, children, ...rest } = props; return jsx("div", { className: cn("aurora-pro glass-relative glass-overflow-hidden", className), ...rest, children: children }); } return jsx(Impl, { ...props }); } const ARGlassMaterialFactory = { createSpatialUI: options => { return new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, userPosition: { value: new THREE.Vector3() }, interactionRadius: { value: options.interactionRadius || 3.0 }, glowIntensity: { value: options.glowIntensity || 1.0 }, color: { value: options.color || new THREE.Color(0.3, 0.7, 1.0) }, opacity: { value: options.opacity || 0.8 } }, vertexShader: ` varying vec3 vPosition; varying vec3 vNormal; varying vec3 vUserPosition; void main() { vPosition = position; vNormal = normal; vUserPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform float time; uniform vec3 userPosition; uniform float interactionRadius; uniform float glowIntensity; uniform vec3 color; uniform float opacity; varying vec3 vPosition; varying vec3 vNormal; varying vec3 vUserPosition; void main() { float distance = length(vUserPosition - userPosition); float interaction = 1.0 - smoothstep(0.0, interactionRadius, distance); vec3 finalColor = color + (glowIntensity * interaction * vec3(0.5, 0.8, 1.0)); float finalOpacity = opacity * (0.8 + 0.2 * interaction); gl_FragColor = vec4(finalColor, finalOpacity); } `, transparent: true, side: THREE.DoubleSide }); }, createHolographicGlass: options => { return new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, color: { value: options.color || new THREE.Color(0.6, 0.8, 1.0) }, opacity: { value: options.opacity || 0.9 }, fresnelPower: { value: options.fresnelPower || 1.5 } }, vertexShader: ` varying vec3 vNormal; varying vec3 vViewDirection; void main() { vNormal = normalize(normalMatrix * normal); vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); vViewDirection = normalize(-mvPosition.xyz); gl_Position = projectionMatrix * mvPosition; } `, fragmentShader: ` uniform float time; uniform vec3 color; uniform float opacity; uniform float fresnelPower; varying vec3 vNormal; varying vec3 vViewDirection; void main() { float fresnel = pow(1.0 - dot(vNormal, vViewDirection), fresnelPower); vec3 finalColor = color * (0.5 + 0.5 * fresnel); float finalOpacity = opacity * (0.7 + 0.3 * fresnel); gl_FragColor = vec4(finalColor, finalOpacity); } `, transparent: true, side: THREE.DoubleSide }); }, createDataVisualization: options => { return new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, lowColor: { value: options.lowColor || new THREE.Color(0.2, 0.4, 1.0) }, highColor: { value: options.highColor || new THREE.Color(1.0, 0.4, 0.2) }, dataValue: { value: options.dataValue || 0.5 } }, vertexShader: ` varying vec3 vPosition; void main() { vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform float time; uniform vec3 lowColor; uniform vec3 highColor; uniform float dataValue; varying vec3 vPosition; void main() { float height = vPosition.y; vec3 finalColor = mix(lowColor, highColor, dataValue); // Add some animation float pulse = sin(time * 2.0) * 0.1 + 0.9; finalColor *= pulse; gl_FragColor = vec4(finalColor, 0.8); } `, transparent: true }); } }; const ARGlassGeometryFactory = { createCurvedPanel: (width, height, depth) => { const geometry = new THREE.PlaneGeometry(width, height, 32, 32); const positions = geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { const x = positions[i]; const y = positions[i + 1]; positions[i + 2] = Math.sin(x * 0.5) * Math.cos(y * 0.5) * depth; } geometry.computeVertexNormals(); return geometry; }, createPortalGeometry: (innerRadius, outerRadius) => { const geometry = new THREE.RingGeometry(innerRadius, outerRadius, 32); const positions = geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { const angle = Math.atan2(positions[i + 1], positions[i]); positions[i + 2] = Math.sin(angle * 4) * 0.1; } geometry.computeVertexNormals(); return geometry; }, createParticleField: count => { const geometry = new THREE.BufferGeometry(); const positions = new Float32Array(count * 3); const colors = new Float32Array(count * 3); for (let i = 0; i < count; i++) { positions[i * 3] = (Math.random() - 0.5) * 10; positions[i * 3 + 1] = (Math.random() - 0.5) * 10; positions[i * 3 + 2] = (Math.random() - 0.5) * 10; colors[i * 3] = Math.random(); colors[i * 3 + 1] = Math.random(); colors[i * 3 + 2] = Math.random(); } geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3)); return geometry; } }; const ARGlassAnimations = { createFloatingAnimation: (object, amplitude = 0.05, frequency = 0.5) => { return time => { object.position.y += Math.sin(time * frequency) * amplitude; }; }, createRotationAnimation: (object, axis, speed = 1) => { return time => { object.rotateOnAxis(axis, speed * 0.01); }; }, createScaleAnimation: (object, minScale, maxScale, frequency = 2) => { return time => { const scale = minScale + (maxScale - minScale) * (Math.sin(time * frequency) * 0.5 + 0.5); object.scale.setScalar(scale); }; } }; const ARGlassInteractions = { createHapticFeedback: (intensity = 0.5, duration = 100) => { if (typeof navigator !== "undefined" && "vibrate" in navigator) { navigator.vibrate(duration * intensity); } }, setupHandTracking: (objects, callback) => { const handleHandMove = handData => { objects.forEach(obj => { if (obj && handData.left.isActive) { const distance = obj.position.distanceTo(handData.left.position); if (distance < 0.5) { callback(obj, handData.left); } } }); }; return handleHandMove; } }; const ARGlassUtils = { createAdaptiveScaling: (minScale, maxScale) => { return distance => { const normalizedDistance = Math.min(Math.max(distance, 1), 10); const scale = maxScale - (normalizedDistance - 1) / 9 * (maxScale - minScale); return Math.max(scale, minScale); }; }, createDistanceBasedOpacity: (maxDistance = 5) => { return distance => { return Math.max(0, 1 - distance / maxDistance); }; } }; let LoadedImpl = null; function ARGlassEffects$1(props) { const [Impl, setImpl] = useState(LoadedImpl); useEffect(() => { let cancelled = false; if (!assertReact19ForThree()) { return; } if (!Impl) { Promise.resolve().then(function () { return ARGlassEffects_r3f; }).then(mod => { if (cancelled) return; const Component = mod.ARGlassEffects || mod.default; LoadedImpl = Component; setImpl(() => Component); }).catch(error => { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.error("[AuraGlass] Failed to load ARGlassEffects R3F module", error); } }); } return () => { cancelled = true; }; }, [Impl]); if (!Impl) { const { className, children } = props; return jsx("div", { className: cn("ar-glass-effects glass-foundation-complete relative", className), children: children }); } return jsx(Impl, { ...props }); } /** * Environment helpers to centralize SSR/browser detection and safe global access. * * CRITICAL: These must check dynamically, NOT cache at module load time, * to handle polyfills and avoid locking in server environment values. */ /** * Dynamic check - re-evaluates on every call to support polyfills */ const hasWindow = () => typeof window !== 'undefined'; const hasDocument = () => typeof document !== 'undefined'; /** * Returns true when executed in a browser-like environment. */ const isBrowser = () => hasWindow() && hasDocument(); /** * Safely access the `window` object when available. */ const getSafeWindow = () => hasWindow() ? window : undefined; /** * Helper to guard feature detection against SSR environments. */ const safeMatchMedia = query => { const win = getSafeWindow(); return win?.matchMedia ? win.matchMedia(query) : undefined; }; /** * Hook to detect if user prefers reduced motion * * CRITICAL: Defaults to `false` (no reduced motion) on both server and initial client render * to prevent SSR hydration mismatches. The actual preference is detected after hydration. */ function useReducedMotion() { // CRITICAL FIX: Start with `false` on both server and client to prevent hydration mismatch // The server cannot access matchMedia, so we default to false (motion allowed) // This matches the first client render, preventing inline style mismatches const [prefersReducedMotion, setPrefersReducedMotion] = useState(false); useEffect(() => { // Detect actual motion preference after hydration if (!isBrowser()) return; const mediaQuery = safeMatchMedia('(prefers-reduced-motion: reduce)'); if (!mediaQuery) return; // Update to actual preference setPrefersReducedMotion(mediaQuery.matches); const handleChange = event => { setPrefersReducedMotion(event.matches); }; if (typeof mediaQuery.addEventListener === 'function') { mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); } if (typeof mediaQuery.addListener === 'function') { mediaQuery.addListener(handleChange); return () => mediaQuery.removeListener(handleChange); } }, []); return prefersReducedMotion; } /** * Simple deterministic pseudo-random number generator for SSR consistency. */ class SeededRandom { constructor(seed = Date.now()) { this.state = SeededRandom.normalizeSeed(seed); } static normalizeSeed(seed) { if (typeof seed === 'number' && Number.isFinite(seed)) { return SeededRandom.hash(seed); } if (typeof seed === 'string') { let hash = 0; for (let i = 0; i < seed.length; i++) { hash = (hash << 5) - hash + seed.charCodeAt(i); hash |= 0; // Convert to 32-bit integer } return SeededRandom.hash(hash); } return SeededRandom.hash(Date.now()); } static hash(input) { let x = input | 0; x ^= x >>> 16; x = Math.imul(x, 0x7feb352d); x ^= x >>> 15; x = Math.imul(x, 0x846ca68b); x ^= x >>> 16; return x >>> 0; } next() { // LCG parameters from Numerical Recipes this.state = 1664525 * this.state + 1013904223 >>> 0; return this.state / 0xffffffff; } nextInRange(min, max) { return min + (max - min) * this.next(); } nextInt(min, max) { return Math.floor(this.nextInRange(min, max + 1)); } nextSigned() { return this.next() * 2 - 1; } } // Shatter geometry factory const ShatterGeometryFactory = { createGlassShard: (size = 1, complexity = 3, random = new SeededRandom()) => { const geometry = new THREE.PlaneGeometry(size, size, complexity, complexity); const positions = geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { const x = positions[i]; const y = positions[i + 1]; const edgeFactor = Math.abs(x) + Math.abs(y); if (edgeFactor > 0.8) { positions[i] += random.nextSigned() * 0.1; positions[i + 1] += random.nextSigned() * 0.1; positions[i + 2] += random.nextSigned() * 0.05; } } geometry.computeVertexNormals(); return geometry; }, createShatterField: (count = 20, _spread = 5, random = new SeededRandom()) => { const geometries = []; for (let i = 0; i < count; i++) { const size = 0.5 + random.next() * 1.5; const complexity = 2 + random.nextInt(0, 2); const geometry = ShatterGeometryFactory.createGlassShard(size, complexity, random); geometries.push(geometry); } return geometries; } }; const ShatterMaterialFactory = { createGlassShardMaterial: (options = {}) => { const { color = new THREE.Color(0.7, 0.9, 1.0), opacity = 0.8, refractionRatio = 1.5, reflectivity = 0.8 } = options; return new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, color: { value: color }, opacity: { value: opacity }, refractionRatio: { value: refractionRatio }, reflectivity: { value: reflectivity }, shatterProgress: { value: 0 } }, vertexShader: ` varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUv; void main() { vPosition = position; vNormal = normal; vUv = uv; vec3 displacedPosition = position; displacedPosition += normal * sin(time * 10.0 + position.x * 5.0) * 0.02; gl_Position = projectionMatrix * modelViewMatrix * vec4(displacedPosition, 1.0); } `, fragmentShader: ` uniform float time; uniform vec3 color; uniform float opacity; uniform float refractionRatio; uniform float reflectivity; uniform float shatterProgress; varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUv; void main() { float fresnel = pow(1.0 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0); vec3 finalColor = color; finalColor += vec3( sin(vPosition.x * 10.0) * 0.1, cos(vPosition.y * 10.0) * 0.1, sin(vPosition.z * 10.0) * 0.1 ); float shatterEffect = sin(time * 20.0 + vPosition.x * 5.0 + vPosition.y * 5.0) * 0.5 + 0.5; finalColor *= (1.0 - shatterProgress * 0.3); float finalOpacity = opacity * (0.7 + 0.3 * fresnel) * (1.0 - shatterProgress * 0.2); gl_FragColor = vec4(finalColor, finalOpacity); } `, transparent: true, side: THREE.DoubleSide }); } }; const ShatterAnimations = { createShardAnimation: (shard, targetPosition, targetRotation, duration = 2) => { const startPosition = shard.position.clone(); const startRotation = shard.rotation.clone(); const startTime = Date.now(); return () => { const elapsed = (Date.now() - startTime) / 1000; const progress = Math.min(elapsed / duration, 1); const easeOut = t => 1 - Math.pow(1 - t, 3); const easedProgress = easeOut(progress); shard.position.lerpVectors(startPosition, targetPosition, easedProgress); shard.rotation.x = THREE.MathUtils.lerp(startRotation.x, targetRotation.x, easedProgress); shard.rotation.y = THREE.MathUtils.lerp(startRotation.y, targetRotation.y, easedProgress); shard.rotation.z = THREE.MathUtils.lerp(startRotation.z, targetRotation.z, easedProgress); return progress >= 1; }; }, createExplosionAnimation: (shards, center, force = 10, duration = 1) => { useReducedMotion(); const animations = shards.map(shard => { const direction = new THREE.Vector3((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2).normalize(); const distance = 2 + Math.random() * 3; const targetPosition = center.clone().add(direction.multiplyScalar(distance)); const targetRotation = new THREE.Euler(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2); return ShatterAnimations.createShardAnimation(shard, targetPosition, targetRotation, duration); }); return () => animations.every(animation => animation()); }, createReformAnimation: (shards, originalPositions, duration = 2) => { const animations = shards.map((shard, index) => { const targetPosition = originalPositions[index]; const targetRotation = new THREE.Euler(0, 0, 0); return ShatterAnimations.createShardAnimation(shard, targetPosition, targetRotation, duration); }); return () => animations.every(animation => animation()); } }; function GlassShatterEffectsR3F(props) { const { children, className = "", trigger = "click", duration = 2, intensity = 1, shardCount = 12, autoReform = true, reformDelay = 3000, onShatter, onReform, disabled = false, showControls = false, seed } = props; const prefersReducedMotion = useReducedMotion(); const containerRef = useRef(null); const canvasRef = useRef(null); const [isShattered, setIsShattered] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const [shards, setShards] = useState([]); const [originalPositions, setOriginalPositions] = useState([]); const [currentAnimation, setCurrentAnimation] = useState(null); useEffect(() => { if (!canvasRef.current) return; const seededRandom = new SeededRandom(seed ?? `${shardCount}`); const geometries = ShatterGeometryFactory.createShatterField(shardCount, 5, seededRandom); const newShards = []; const positions = []; geometries.forEach(geometry => { const material = ShatterMaterialFactory.createGlassShardMaterial({ opacity: 0.8 - seededRandom.next() * 0.3, refractionRatio: 1.3 + seededRandom.next() * 0.4 }); const shard = new THREE.Mesh(geometry, material); shard.position.set(seededRandom.nextSigned() * 2, seededRandom.nextSigned() * 2, 0); shard.rotation.set(seededRandom.next() * Math.PI * 2, seededRandom.next() * Math.PI * 2, seededRandom.next() * Math.PI * 2); newShards.push(shard); positions.push(shard.position.clone()); }); setShards(newShards); setOriginalPositions(positions); }, [shardCount, seed]); const triggerReform = useCallback(() => { if (!isShattered || isAnimating) return; setIsAnimating(true); onReform?.(); const animation = ShatterAnimations.createReformAnimation(shards, originalPositions, duration); setCurrentAnimation(() => animation); }, [isShattered, isAnimating, shards, originalPositions, duration, onReform]); const triggerShatter = useCallback(() => { if (disabled || isAnimating) return; setIsShattered(true); setIsAnimating(true); onShatter?.(); const center = new THREE.Vector3(0, 0, 0); const animation = ShatterAnimations.createExplosionAnimation(shards, center, intensity * 10, duration); setCurrentAnimation(() => animation); if (autoReform) { setTimeout(() => { triggerReform(); }, reformDelay); } }, [disabled, isAnimating, shards, intensity, duration, autoReform, reformDelay, onShatter, triggerReform]); const handleManualTrigger = useCallback(() => { if (isShattered) { triggerReform(); } else { triggerShatter(); } }, [isShattered, triggerReform, triggerShatter]); useEffect(() => { if (disabled) return; const element = containerRef.current; if (!element) return; const handleClick = () => { if (trigger === "click") handleManualTrigger(); }; const handleMouseEnter = () => { if (trigger === "hover") triggerShatter(); }; const handleMouseLeave = () => { if (trigger === "hover" && autoReform) { setTimeout(() => triggerReform(), reformDelay); } }; if (trigger === "click") { element.addEventListener("click", handleClick); } else if (trigger === "hover") { element.addEventListener("mouseenter", handleMouseEnter); element.addEventListener("mouseleave", handleMouseLeave); } return () => { element.removeEventListener("click", handleClick); element.removeEventListener("mouseenter", handleMouseEnter); element.removeEventListener("mouseleave", handleMouseLeave); }; }, [trigger, disabled, handleManualTrigger, triggerShatter, triggerReform, autoReform, reformDelay]); useEffect(() => { if (trigger === "auto" && !disabled) { const interval = setInterval(() => { if (!isAnimating) triggerShatter(); }, 5000); return () => clearInterval(interval); } }, [trigger, disabled, isAnimating, triggerShatter]); return jsxs("div", { ref: containerRef, className: cn("glass-shatter-effects glass-relative glass-overflow-hidden", className), style: { position: "relative", cursor: trigger === "click" ? "pointer" : "default" }, children: [jsx("div", { className: cn("content glass-transition-opacity glass-duration-300", isShattered ? "glass-opacity-0" : "glass-opacity-100"), children: children }), jsx(AnimatePresence, { children: isShattered && jsx(motion.div, { initial: { opacity: 0 }, animate: prefersReducedMotion ? {} : { opacity: 1 }, exit: { opacity: 0 }, className: cn("glass-absolute glass-inset-0 glass-pointer-events-none"), children: jsx(Canvas, { ref: canvasRef, className: cn("glass-w-full glass-h-full"), camera: { position: [0, 0, 5], fov: 75 }, gl: { alpha: true, antialias: true }, children: jsx(ShatterScene, { shards: shards, currentAnimation: currentAnimation, setCurrentAnimation: setCurrentAnimation, setIsAnimating: setIsAnimating, setIsShattered: setIsShattered, isShattered: isShattered }) }) }) }), showControls && jsxs(motion.div, { initial: { opacity: 0, y: 20 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, className: cn("glass-absolute glass-bottom-4 glass-right-4 glass-flex glass-gap-2"), children: [jsx("button", { onClick: handleManualTrigger, disabled: isAnimating, className: cn("glass-p-2 glass-surface-subtle glass-foundation-complete glass-radius-lg glass-border glass-border-primary hover:glass-surface-hover glass-transition-colors disabled:glass-opacity-50 glass-focus glass-touch-target glass-contrast-guard"), title: isShattered ? "Reform" : "Shatter", children: isShattered ? jsx(RotateCcw, { className: cn("glass-w-4 glass-h-4") }) : jsx(Zap, { className: cn("glass-w-4 glass-h-4") }) }), isAnimating && jsxs("div", { className: cn("glass-flex glass-items-center glass-gap-2 glass-px-3 glass-py-2 glass-surface-info glass-foundation-complete glass-radius-lg glass-text-info glass-text-sm"), children: [jsx(motion.div, { animate: prefersReducedMotion ? {} : { rotate: 360 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 1, repeat: Infinity, ease: "linear" }, children: jsx(Triangle, { className: cn("glass-w-3 glass-h-3") }) }), "Animating..."] })] })] }); } function ShatterScene({ shards, currentAnimation, setCurrentAnimation, setIsAnimating, setIsShattered, isShattered }) { const { scene } = useThree(); useEffect(() => { shards.forEach(shard => { scene.add(shard); }); return () => { shards.forEach(shard => { scene.remove(shard); }); }; }, [shards, scene]); useFrame(state => { const time = state.clock.elapsedTime; shards.forEach(shard => { const material = shard.material; if (material && material.uniforms) { if (material.uniforms.time) material.uniforms.time.value = time; if (material.uniforms.shatterProgress) material.uniforms.shatterProgress.value = isShattered ? 1 : 0; } }); if (currentAnimation) { const complete = currentAnimation(); if (complete) { setCurrentAnimation(null); setIsAnimating(false); if (!isShattered) setIsShattered(false); } } }); return jsxs(Fragment, { children: [jsx("ambientLight", { intensity: 0.4 }), jsx("pointLight", { position: [10, 10, 10], intensity: 0.8 }), jsx("pointLight", { position: [-10, -10, -10], intensity: 0.3, color: 0x4488ff })] }); } var GlassShatterEffects_r3f = /*#__PURE__*/Object.freeze({ __proto__: null, GlassShatterEffectsR3F: GlassShatterEffectsR3F, default: GlassShatterEffectsR3F }); // Seasonal particle systems const SeasonalParticleFactory = { // Snow particles createSnowParticle: (size = 1, complexity = 6) => { const geometry = new THREE.ConeGeometry(size * 0.1, size * 0.3, complexity); return geometry; }, // Leaf particles createLeafParticle: (size = 1) => { const geometry = new THREE.PlaneGeometry(size, size * 1.5); // Add some curvature to make it look like a leaf const positions = geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { const x = positions[i]; const y = positions[i + 1]; positions[i + 2] = Math.sin(x * 5) * 0.1 * Math.abs(y); } geometry.computeVertexNormals(); return geometry; }, // Flower petals createPetalParticle: (size = 1) => { const geometry = new THREE.PlaneGeometry(size, size * 2); // Curve the petal const positions = geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { const x = positions[i]; const y = positions[i + 1]; positions[i + 2] = Math.sin(x * 3) * 0.2 * (1 - Math.abs(y)); } geometry.computeVertexNormals(); return geometry; }, // Sun rays createSunRayParticle: (length = 1, width = 0.1) => { const geometry = new THREE.CylinderGeometry(width, width * 0.5, length, 8); return geometry; }, // Rain drops createRainDropParticle: (length = 1) => { const geometry = new THREE.CylinderGeometry(0.02, 0.02, length, 6); return geometry; }, // Create particle field for season createSeasonalField: (season, count = 50, random = new SeededRandom()) => { const particles = []; for (let i = 0; i < count; i++) { let geometry; let material; switch (season) { case "winter": geometry = SeasonalParticleFactory.createSnowParticle(0.5 + random.next() * 0.5); material = SeasonalParticleFactory.createSnowMaterial(); break; case "autumn": geometry = SeasonalParticleFactory.createLeafParticle(0.3 + random.next() * 0.4); material = SeasonalParticleFactory.createLeafMaterial(); break; case "spring": geometry = SeasonalParticleFactory.createPetalParticle(0.2 + random.next() * 0.3); material = SeasonalParticleFactory.createPetalMaterial(random); break; case "summer": geometry = SeasonalParticleFactory.createSunRayParticle(1 + random.next() * 2); material = SeasonalParticleFactory.createSunRayMaterial(); break; default: geometry = new THREE.SphereGeometry(0.1); material = new THREE.MeshBasicMaterial({ color: 0xffffff }); } const particle = new THREE.Mesh(geometry, material); particle.position.set(random.nextSigned() * 10, random.next() * 15, random.nextSigned() * 5); particle.rotation.set(random.next() * Math.PI * 2, random.next() * Math.PI * 2, random.next() * Math.PI * 2); // Add custom properties for animation particle.userData = { originalY: particle.position.y, fallSpeed: 0.01 + random.next() * 0.05, rotationSpeed: random.nextSigned() * 0.02, windOffset: random.next() * Math.PI * 2, season }; particles.push(particle); } return particles; }, // Material factories createSnowMaterial: () => new THREE.MeshPhongMaterial({ color: 0xffffff, transparent: true, opacity: 0.8, shininess: 100 }), createLeafMaterial: () => new THREE.MeshPhongMaterial({ color: new THREE.Color().setHSL(0.1, 0.8, 0.4), transparent: true, opacity: 0.9, side: THREE.DoubleSide }), createPetalMaterial: random => new THREE.MeshPhongMaterial({ color: new THREE.Color().setHSL(random.nextInRange(0.8, 1.0), 0.8, 0.6), transparent: true, opacity: 0.8, side: THREE.DoubleSide }), createSunRayMaterial: () => new THREE.MeshBasicMaterial({ color: new THREE.Color(1, 0.9, 0.3), transparent: true, opacity: 0.6 }), createRainMaterial: () => new THREE.MeshBasicMaterial({ color: new THREE.Color(0.5, 0.7, 1), transparent: true, opacity: 0.7 }) }; // Seasonal animation system const SeasonalAnimations = { // Snow falling animation snowFall: (particle, time, windStrength = 1) => { particle.position.y -= particle.userData.fallSpeed; particle.position.x += Math.sin(time * 2 + particle.userData.windOffset) * 0.01 * windStrength; particle.rotation.x += particle.userData.rotationSpeed; particle.rotation.y += particle.userData.rotationSpeed * 0.5; // Reset particle when it falls below screen if (particle.position.y < -10) { particle.position.y = 15 + Math.random() * 5; particle.position.x = (Math.random() - 0.5) * 20; } }, // Leaf falling animation leafFall: (particle, time, windStrength = 1) => { particle.position.y -= particle.userData.fallSpeed; particle.position.x += Math.sin(time * 1.5 + particle.userData.windOffset) * 0.02 * windStrength; particle.rotation.x += particle.userData.rotationSpeed; particle.rotation.z += particle.userData.rotationSpeed * 2; if (particle.position.y < -10) { particle.position.y = 15 + Math.random() * 5; particle.position.x = (Math.random() - 0.5) * 20; } }, // Petal floating animation petalFloat: (particle, time, windStrength = 1) => { particle.position.y -= particle.userData.fallSpeed * 0.5; particle.position.x += Math.sin(time * 1 + particle.userData.windOffset) * 0.015 * windStrength; particle.rotation.y += particle.userData.rotationSpeed; particle.rotation.x += Math.sin(time * 3 + particle.userData.windOffset) * 0.01; if (particle.position.y < -10) { particle.position.y = 15 + Math.random() * 5; particle.position.x = (Math.random() - 0.5) * 20; } }, // Sun ray pulsing animation sunRayPulse: (particle, time) => { const pulse = Math.sin(time * 4) * 0.3 + 0.7; particle.scale.y = pulse; if (particle.material && "opacity" in particle.material) { particle.material.opacity = 0.3 + pulse * 0.4; } }, // Rain falling animation rainFall: (particle, time, windStrength = 1) => { particle.position.y -= particle.userData.fallSpeed * 3; particle.position.x += windStrength * 0.05; if (particle.position.y < -10) { particle.position.y = 15 + Math.random() * 5; particle.position.x = (Math.random() - 0.5) * 20; } } }; function SeasonalParticlesR3F({ season = "auto", particleCount = 30, windStrength = 1, animationSpeed = 1, className = "", showControls = false, autoSeason = true, seasonDuration = 10000, // 10 seconds per season onSeasonChange, children, seed }) { const prefersReducedMotion = useReducedMotion(); const canvasRef = useRef(null); const [currentSeason, setCurrentSeason] = useState(season === "auto" ? "winter" : season); const [particles, setParticles] = useState([]); const [isPlaying, setIsPlaying] = useState(true); const [seasonIndex, setSeasonIndex] = useState(0); const seasons = ["winter", "spring", "summer", "autumn"]; // Initialize particles for current season useEffect(() => { const seededRandom = new SeededRandom(seed ?? `${currentSeason}-${particleCount}`); const newParticles = SeasonalParticleFactory.createSeasonalField(currentSeason, particleCount, seededRandom); setParticles(newParticles); onSeasonChange?.(currentSeason); }, [currentSeason, particleCount, onSeasonChange, seed]); // Auto season rotation useEffect(() => { if (!autoSeason || season !== "auto") return; const interval = setInterval(() => { setSeasonIndex(prev => { const next = (prev + 1) % seasons.length; setCurrentSeason(seasons[next]); return next; }); }, seasonDuration); return () => clearInterval(interval); }, [autoSeason, season, seasonDuration, seasons]); // Manual season control const changeSeason = useCallback(newSeason => { setCurrentSeason(newSeason); setSeasonIndex(seasons.indexOf(newSeason)); }, [seasons]); // Play/pause control const togglePlay = useCallback(() => { setIsPlaying(prev => !prev); }, []); // Get season icon const getSeasonIcon = seasonName => { switch (seasonName) { case "winter": return jsx(Snowflake, { className: cn("glass-w-4 glass-h-4") }); case "spring": return jsx(Flower, { className: cn("glass-w-4 glass-h-4") }); case "summer": return jsx(Sun, { className: cn("glass-w-4 glass-h-4") }); case "autumn": return jsx(Leaf, { className: cn("glass-w-4 glass-h-4") }); default: return jsx(Cloud, { className: cn("glass-w-4 glass-h-4") }); } }; // Get season colors for UI const getSeasonColors = seasonName => { switch (seasonName) { case "winter": return "text-blue-400 border-blue-400/30 bg-blue-400/10"; case "spring": return "text-pink-400 border-pink-400/30 bg-pink-400/10"; case "summer": return "text-yellow-400 border-yellow-400/30 bg-yellow-400/10"; case "autumn": return "text-orange-400 border-orange-400/30 bg-orange-400/10"; default: return "text-gray-400 border-gray-400/30 bg-gray-400/10"; } }; return jsxs("div", { className: cn("seasonal-particles glass-relative glass-overflow-hidden", className), children: [jsx(Canvas, { ref: canvasRef, className: cn("glass-absolute glass-inset-0 glass-pointer-events-none"), camera: { position: [0, 5, 10], fov: 75 }, gl: { alpha: true, antialias: true }, children: jsx(SeasonalScene, { particles: particles, currentSeason: currentSeason, windStrength: windStrength, animationSpeed: animationSpeed, isPlaying: isPlaying }) }), jsx("div", { className: cn("glass-relative glass-z-10"), children: children }), jsx(motion.div, { initial: { opacity: 0, y: 20 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, className: cn("glass-absolute glass-top-4 glass-left-4 glass-px-3 glass-py-2 glass-radius-lg glass-foundation-complete glass-border", getSeasonColors(currentSeason)), children: jsxs("div", { className: cn("glass-flex glass-items-center glass-gap-2"), children: [getSeasonIcon(currentSeason), jsx("span", { className: cn("glass-text-sm glass-font-medium glass-capitalize"), children: currentSeason })] }) }), showControls && jsxs(motion.div, { initial: { opacity: 0, y: 20 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, className: 'glass-absolute glass-bottom-4 glass-right-4 glass-flex glass-flex-col glass-gap-2', children: [jsx("div", { className: "glass-flex glass-gap-2 glass-p-2 glass-surface-dark/20 glass-backdrop-blur-lg glass-radius-lg glass-border glass-border-white/10 glass-contrast-guard", children: seasons.map(seasonName => jsx("button", { onClick: () => changeSeason(seasonName), className: `p-2 rounded-lg transition-colors glass-focus glass-touch-target glass-contrast-guard ${currentSeason === seasonName ? "bg-white/20 text-white" : "text-white/60 hover:text-white hover:bg-white/10"}`, title: `Switch to ${seasonName}`, children: getSeasonIcon(seasonName) }, seasonName)) }), jsxs("div", { className: "glass-flex glass-gap-2 glass-p-2 glass-surface-dark/20 glass-backdrop-blur-lg glass-radius-lg glass-border glass-border-white/10 glass-contrast-guard", children: [jsx("button", { onClick: togglePlay, className: 'glass-p-2 glass-radius-lg glass-text-primary hover:glass-surface-subtle/10 glass-transition-colors glass-focus glass-touch-target glass-contrast-guard', title: isPlaying ? "Pause" : "Play", children: isPlaying ? jsx(Pause, { className: 'glass-w-4 glass-h-4' }) : jsx(Play, { className: 'glass-w-4 glass-h-4' }) }), jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 glass-text-primary-glass-opacity-60 glass-text-sm', children: [jsx(Wind, { className: 'glass-w-3 glass-h-3' }), jsx("span", { children: windStrength.toFixed(1) })] })] })] }), windStrength > 0 && jsx(motion.div, { initial: { opacity: 0, x: -20 }, animate: prefersReducedMotion ? {} : { opacity: 1, x: 0 }, className: 'glass-absolute glass-top-4 glass-right-4 glass-px-3 glass-py-2 glass-surface-dark/20 glass-backdrop-blur-lg glass-radius-lg glass-border glass-border-white/10 glass-contrast-guard', children: jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 glass-text-primary-glass-opacity-60 glass-text-sm', children: [jsx(motion.div, { animate: prefersReducedMotion ? {} : { x: windStrength > 0 ? [0, 5, 0] : 0 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 2, repeat: Infinity, ease: "easeInOut" }, children: jsx(Wind, { className: 'glass-w-3 glass-h-3' }) }), jsxs("span", { children: ["Wind: ", windStrength.toFixed(1)] })] }) })] }); } // 3D Seasonal Scene Component function SeasonalScene({ particles, currentSeason, windStrength, animationSpeed, isPlaying }) { const { scene } = useThree(); // Add particles to scene useEffect(() => { particles.forEach(particle => { scene.add(particle); }); return () => { particles.forEach(particle => { scene.remove(particle); }); }; }, [particles, scene]); // Animation loop useFrame(state => { if (!isPlaying) return; const time = state.clock.elapsedTime * animationSpeed; particles.forEach(particle => { if (!particle.userData) return; switch (particle.userData.season || currentSeason) { case "winter": SeasonalAnimations.snowFall(particle, time, windStrength); break; case "autumn": SeasonalAnimations.leafFall(particle, time, windStrength); break; case "spring": SeasonalAnimations.petalFloat(particle, time, windStrength); break; case "summer": SeasonalAnimations.sunRayPulse(particle, time); break; } }); }); return jsxs(Fragment, { children: [jsx("ambientLight", { intensity: 0.3 }), currentSeason === "summer" && jsx("directionalLight", { position: [10, 10, 5], intensity: 1.2, color: 0xffeb3b }), currentSeason === "winter" && jsx("directionalLight", { position: [5, 5, 10], intensity: 0.8, color: 0xe3f2fd }), currentSeason === "autumn" && jsx("directionalLight", { position: [5, 10, 5], intensity: 0.9, color: 0xff9800 }), currentSeason === "spring" && jsx("directionalLight", { position: [10, 5, 5], intensity: 1.0, color: 0xe91e63 })] }); } var SeasonalParticles_r3f = /*#__PURE__*/Object.freeze({ __proto__: null, SeasonalParticlesR3F: SeasonalParticlesR3F, default: SeasonalParticlesR3F }); // Aurora geometry and material factories const AuroraFactory = { // Create aurora wave geometry createAuroraWave: (width = 20, height = 10, segments = 64) => { const geometry = new THREE.PlaneGeometry(width, height, segments, 32); // Add wave-like deformation const positions = geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) {