UNPKG

aura-glass

Version:

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

582 lines (579 loc) 21.2 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { forwardRef, useRef, useState, useCallback, useEffect } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { useA11yId } from '../../utils/a11y.js'; import { useMotionPreferenceContext } from '../../contexts/MotionPreferenceContext.js'; import { useGlassSound } from '../../utils/soundDesign.js'; const GlassAuroraDisplay = /*#__PURE__*/forwardRef(({ width = 800, height = 500, intensity = 0.7, solarWindStrength = 0.6, geomagneticActivity = 0.5, colorPreset = "classic", showStars = true, showSolarWind = true, animationSpeed = 1, waveComplexity = 1, layerCount = 4, observationTime = 22, latitude = 65, showAtmosphericGlow = true, activityLevel = "moderate", realTimeMode = false, onAuroraChange, onSolarEvent, showControls = true, showAuroraInfo = true, respectMotionPreference = true, className, ...props }, ref) => { const { prefersReducedMotion, isMotionSafe } = useMotionPreferenceContext(); const { play } = useGlassSound(); const canvasRef = useRef(null); const animationRef = useRef(); const auroraDisplayId = useA11yId("glass-aurora-display"); const [auroraLayers, setAuroraLayers] = useState([]); const [solarWindParticles, setSolarWindParticles] = useState([]); const [starField, setStarField] = useState({ stars: [], shootingStars: [], id: "starfield" }); const [animationTime, setAnimationTime] = useState(0); const [currentIntensity, setCurrentIntensity] = useState(intensity); const [activeColors, setActiveColors] = useState([]); // Aurora color presets const colorPresets = { classic: [[0, 255, 146], // Green (oxygen 557.7nm) [255, 0, 255], // Magenta (nitrogen) [0, 255, 255], // Cyan (oxygen 630nm) [255, 255, 0] // Yellow (sodium) ], rare: [[255, 0, 0], // Red (high altitude oxygen) [128, 0, 128], // Purple (nitrogen) [255, 165, 0], // Orange (sodium) [255, 192, 203] // Pink (nitrogen) ], storm: [[255, 255, 255], // White (intense activity) [255, 0, 0], // Red (storm conditions) [255, 100, 100], // Light red [200, 200, 255] // Blue-white ], sunset: [[255, 140, 0], // Dark orange [255, 69, 0], // Red-orange [255, 20, 147], // Deep pink [138, 43, 226] // Blue violet ], cosmic: [[75, 0, 130], // Indigo [148, 0, 211], // Dark violet [0, 191, 255], // Deep sky blue [127, 255, 212] // Aquamarine ] }; // Initialize star field const initializeStarField = useCallback(() => { const stars = Array.from({ length: 200 }, (_, i) => ({ x: Math.random() * width, y: Math.random() * height * 0.6, // Stars in upper portion brightness: Math.random() * 0.8 + 0.2, twinklePhase: Math.random() * Math.PI * 2 })); setStarField(prev => ({ ...prev, stars })); }, [width, height]); // Generate aurora layers const generateAuroraLayers = useCallback(() => { const colors = colorPresets[colorPreset]; const layers = []; for (let i = 0; i < layerCount; i++) { const points = []; const baseY = height * (0.3 + i * 0.15); const layerHeight = 60 + i * 20; // Generate wave points for (let x = 0; x <= width; x += 10) { points.push({ x, y: baseY + Math.sin(x * 0.01 + i) * (20 + waveComplexity * 10), intensity: Math.random() * 0.5 + 0.5 }); } layers.push({ points, color: colors[i % colors.length], opacity: currentIntensity * 0.8 * (1 - i * 0.2), waveOffset: i * Math.PI / 4, waveSpeed: 0.02 + i * 0.01, height: layerHeight, id: `aurora-layer-${i}` }); } setAuroraLayers(layers); setActiveColors(colors.map(c => `rgb(${c[0]}, ${c[1]}, ${c[2]})`)); onAuroraChange?.(currentIntensity, colors.map(c => `rgb(${c[0]}, ${c[1]}, ${c[2]})`)); }, [colorPreset, layerCount, width, height, waveComplexity, currentIntensity, onAuroraChange]); // Generate solar wind particles const generateSolarWindParticles = useCallback(() => { if (!showSolarWind) return; const particleCount = Math.floor(solarWindStrength * 50); const particles = []; for (let i = 0; i < particleCount; i++) { particles.push({ x: Math.random() * width, y: -10, vx: (Math.random() - 0.5) * 2, vy: Math.random() * 3 + 2, size: Math.random() * 2 + 1, opacity: Math.random() * 0.6 + 0.2, color: [255, 255, 255], energy: Math.random() * solarWindStrength, lifetime: Math.random() * 3000 + 1000, id: `solar-particle-${i}` }); } setSolarWindParticles(particles); }, [showSolarWind, solarWindStrength, width]); // Initialize all components useEffect(() => { initializeStarField(); generateAuroraLayers(); generateSolarWindParticles(); }, [initializeStarField, generateAuroraLayers, generateSolarWindParticles]); // Update aurora layers const updateAuroraLayers = useCallback(deltaTime => { setAuroraLayers(prevLayers => prevLayers.map(layer => ({ ...layer, waveOffset: layer.waveOffset + layer.waveSpeed * deltaTime * animationSpeed, opacity: Math.max(0, layer.opacity + (Math.random() - 0.5) * 0.02 * geomagneticActivity), points: layer.points.map((point, index) => ({ ...point, y: point.y + Math.sin(layer.waveOffset + index * 0.1) * (2 + geomagneticActivity * 3), intensity: Math.max(0.3, Math.min(1, point.intensity + (Math.random() - 0.5) * 0.1)) })) }))); }, [animationSpeed, geomagneticActivity]); // Update solar wind particles const updateSolarWindParticles = useCallback(deltaTime => { setSolarWindParticles(prevParticles => { const updated = prevParticles.map(particle => ({ ...particle, x: particle.x + particle.vx * deltaTime * animationSpeed, y: particle.y + particle.vy * deltaTime * animationSpeed, lifetime: particle.lifetime - deltaTime, opacity: Math.max(0, particle.opacity - deltaTime * 0.0005) })).filter(particle => particle.lifetime > 0 && particle.x > -50 && particle.x < width + 50 && particle.y < height + 50); // Add new particles if (updated.length < solarWindStrength * 30) { const newParticles = Array.from({ length: Math.min(5, Math.floor(solarWindStrength * 30) - updated.length) }, (_, i) => ({ x: Math.random() * width, y: -10, vx: (Math.random() - 0.5) * 2, vy: Math.random() * 3 + 2, size: Math.random() * 2 + 1, opacity: Math.random() * 0.6 + 0.2, color: [255, 255, 255], energy: Math.random() * solarWindStrength, lifetime: Math.random() * 3000 + 1000, id: `solar-particle-new-${i}-${Date.now()}` })); return [...updated, ...newParticles]; } return updated; }); }, [animationSpeed, solarWindStrength, width, height]); // Update shooting stars const updateShootingStars = useCallback(deltaTime => { setStarField(prevField => { const updatedShootingStars = prevField.shootingStars.map(star => ({ ...star, x: star.x + star.vx * deltaTime * animationSpeed, y: star.y + star.vy * deltaTime * animationSpeed, lifetime: star.lifetime - deltaTime, trail: [{ x: star.x, y: star.y, opacity: 1 }, ...star.trail.map((t, i) => ({ ...t, opacity: t.opacity * 0.95 }))].slice(0, 10) })).filter(star => star.lifetime > 0 && star.x < width + 100); // Occasionally add new shooting stars if (Math.random() < 0.001 * animationSpeed) { updatedShootingStars.push({ x: Math.random() * width, y: Math.random() * height * 0.3, vx: Math.random() * 3 + 2, vy: Math.random() * 2 + 1, lifetime: Math.random() * 2000 + 1000, trail: [] }); play("success"); } return { ...prevField, shootingStars: updatedShootingStars }; }); }, [animationSpeed, width, height, play]); // Solar activity simulation useEffect(() => { if (!realTimeMode) return; const interval = setInterval(() => { // Simulate solar events if (Math.random() < 0.1) { const eventType = Math.random() < 0.3 ? "flare" : Math.random() < 0.6 ? "wind" : "storm"; onSolarEvent?.(eventType); // Adjust parameters based on event switch (eventType) { case "flare": setCurrentIntensity(prev => Math.min(1, prev + 0.3)); break; case "wind": // Handled in particle updates break; case "storm": setCurrentIntensity(1); play("error"); break; } } }, 10000); return () => clearInterval(interval); }, [realTimeMode, onSolarEvent, play]); // Render aurora display const render = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; // Clear canvas with night sky gradient const skyGradient = ctx.createLinearGradient(0, 0, 0, height); const isDarkTime = observationTime < 6 || observationTime > 18; if (isDarkTime) { skyGradient.addColorStop(0, "rgb(10, 10, 30)"); skyGradient.addColorStop(0.6, "rgb(5, 5, 15)"); skyGradient.addColorStop(1, "rgb(0, 0, 5)"); } else { skyGradient.addColorStop(0, "rgb(70, 130, 180)"); skyGradient.addColorStop(1, "rgb(25, 25, 112)"); } ctx.fillStyle = skyGradient; ctx.fillRect(0, 0, width, height); // Draw stars if (showStars && isDarkTime) { starField.stars.forEach(star => { const twinkle = Math.sin(animationTime * 0.003 + star.twinklePhase) * 0.3 + 0.7; ctx.globalAlpha = star.brightness * twinkle; ctx.fillStyle = "white"; ctx.beginPath(); ctx.arc(star.x, star.y, 1, 0, Math.PI * 2); ctx.fill(); }); // Draw shooting stars starField.shootingStars.forEach(star => { star.trail.forEach((point, index) => { ctx.globalAlpha = point.opacity * (1 - index * 0.1); ctx.fillStyle = "white"; ctx.beginPath(); ctx.arc(point.x, point.y, 2 - index * 0.2, 0, Math.PI * 2); ctx.fill(); }); }); } // Draw atmospheric glow if (showAtmosphericGlow && isDarkTime) { const glowGradient = ctx.createRadialGradient(width / 2, height * 0.8, 0, width / 2, height * 0.8, width); glowGradient.addColorStop(0, "rgba(100, 200, 100, 0.1)"); glowGradient.addColorStop(1, "rgba(100, 200, 100, 0)"); ctx.fillStyle = glowGradient; ctx.fillRect(0, 0, width, height); } // Draw aurora layers ctx.globalAlpha = 1; auroraLayers.forEach(layer => { if (layer.opacity < 0.01) return; // Create gradient for aurora band const gradient = ctx.createLinearGradient(0, 0, 0, layer.height); gradient.addColorStop(0, `rgba(${layer.color[0]}, ${layer.color[1]}, ${layer.color[2]}, 0)`); gradient.addColorStop(0.5, `rgba(${layer.color[0]}, ${layer.color[1]}, ${layer.color[2]}, ${layer.opacity})`); gradient.addColorStop(1, `rgba(${layer.color[0]}, ${layer.color[1]}, ${layer.color[2]}, 0)`); ctx.fillStyle = gradient; ctx.beginPath(); // Draw aurora curve ctx.moveTo(layer.points[0].x, layer.points[0].y + layer.height / 2); layer.points.forEach((point, index) => { if (index === 0) return; const prevPoint = layer.points[index - 1]; const intensityFactor = (point.intensity + prevPoint.intensity) / 2; const waveY = point.y + Math.sin(animationTime * 0.001 + index * 0.1) * intensityFactor * 10; ctx.lineTo(point.x, waveY); }); // Complete the shape for (let i = layer.points.length - 1; i >= 0; i--) { const point = layer.points[i]; const intensityFactor = point.intensity; const waveY = point.y + Math.sin(animationTime * 0.001 + i * 0.1) * intensityFactor * 10; ctx.lineTo(point.x, waveY + layer.height); } ctx.closePath(); ctx.fill(); // Add shimmer effect ctx.save(); ctx.globalCompositeOperation = "overlay"; ctx.globalAlpha = 0.3; const shimmerGradient = ctx.createLinearGradient(animationTime * 0.1 % width, 0, (animationTime * 0.1 + 100) % width, 0); shimmerGradient.addColorStop(0, "rgba(255, 255, 255, 0)"); shimmerGradient.addColorStop(0.5, "rgba(var(--glass-color-white) / var(--glass-opacity-80))"); shimmerGradient.addColorStop(1, "rgba(255, 255, 255, 0)"); ctx.fillStyle = shimmerGradient; ctx.fill(); ctx.restore(); }); // Draw solar wind particles if (showSolarWind) { solarWindParticles.forEach(particle => { ctx.globalAlpha = particle.opacity; ctx.fillStyle = `rgb(${particle.color[0]}, ${particle.color[1]}, ${particle.color[2]})`; // Add glow effect ctx.shadowBlur = particle.energy * 10; ctx.shadowColor = `rgb(${particle.color[0]}, ${particle.color[1]}, ${particle.color[2]})`; ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; }); } // Aurora info overlay if (showAuroraInfo) { ctx.save(); ctx.fillStyle = "var(--glass-text-secondary-dark)"; ctx.fillRect(10, 10, 220, 160); ctx.fillStyle = "white"; ctx.font = "14px sans-serif"; ctx.fillText(`Aurora Intensity: ${Math.round(currentIntensity * 100)}%`, 20, 30); ctx.fillText(`Activity Level: ${activityLevel}`, 20, 50); ctx.fillText(`Solar Wind: ${Math.round(solarWindStrength * 100)}%`, 20, 70); ctx.fillText(`Geomagnetic: ${Math.round(geomagneticActivity * 100)}%`, 20, 90); ctx.fillText(`Latitude: ${latitude}°`, 20, 110); ctx.fillText(`Time: ${Math.floor(observationTime)}:${String(Math.floor(observationTime % 1 * 60)).padStart(2, "0")}`, 20, 130); ctx.fillText(`Visibility: ${isDarkTime ? "Good" : "Poor"}`, 20, 150); ctx.restore(); } }, [width, height, observationTime, showStars, starField, animationTime, showAtmosphericGlow, auroraLayers, showSolarWind, solarWindParticles, showAuroraInfo, currentIntensity, activityLevel, solarWindStrength, geomagneticActivity, latitude]); // Animation loop useEffect(() => { if (prefersReducedMotion && respectMotionPreference) { render(); return; } const animate = currentTime => { const deltaTime = 16; // 60fps setAnimationTime(prev => prev + deltaTime); updateAuroraLayers(deltaTime); updateSolarWindParticles(deltaTime); updateShootingStars(deltaTime); render(); animationRef.current = requestAnimationFrame(animate); }; animationRef.current = requestAnimationFrame(animate); return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [prefersReducedMotion, respectMotionPreference, render, updateAuroraLayers, updateSolarWindParticles, updateShootingStars]); // Canvas setup useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; canvas.width = width; canvas.height = height; }, [width, height]); // Controls const renderControls = () => { if (!showControls) return null; return jsxs(OptimizedGlassCore, { elevation: "level2", intensity: "medium", depth: 1, tint: "neutral", border: "subtle", className: "glass-aurora-controls glass-flex glass-flex-wrap glass-items-center glass-gap-4 glass-p-4 glass-radius-lg glass-glass-glass-backdrop-blur-md glass-border glass-border-glass-border/20 glass-contrast-guard", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("label", { className: "glass-text-sm", children: "Intensity:" }), jsx("input", { type: "range", min: "0", max: "1", step: "0.1", value: currentIntensity, onChange: e => setCurrentIntensity(parseFloat(e.target.value)), className: 'w-20' }), jsxs("span", { className: 'glass-text-sm min-w-[3ch]', children: [Math.round(currentIntensity * 100), "%"] })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("label", { className: "glass-text-sm", children: "Colors:" }), jsxs("select", { value: colorPreset, onChange: e => {}, className: "glass-px-2 glass-py-1 glass-radius-md glass-surface-overlay glass-border glass-border-glass-border/20 glass-contrast-guard", children: [jsx("option", { value: "classic", children: "Classic" }), jsx("option", { value: "rare", children: "Rare" }), jsx("option", { value: "storm", children: "Storm" }), jsx("option", { value: "sunset", children: "Sunset" }), jsx("option", { value: "cosmic", children: "Cosmic" })] })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("label", { className: "glass-text-sm", children: "Activity:" }), jsxs("select", { value: activityLevel, onChange: e => {}, className: "glass-px-2 glass-py-1 glass-radius-md glass-surface-overlay glass-border glass-border-glass-border/20 glass-contrast-guard", children: [jsx("option", { value: "low", children: "Low" }), jsx("option", { value: "moderate", children: "Moderate" }), jsx("option", { value: "high", children: "High" }), jsx("option", { value: "storm", children: "Storm" })] })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("label", { className: "glass-text-sm", children: "Time:" }), jsx("input", { type: "range", min: "0", max: "24", step: "0.5", value: observationTime, onChange: e => {}, className: 'w-20' })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsxs("label", { className: "glass-text-sm", children: [jsx("input", { type: "checkbox", checked: showStars, onChange: e => {}, className: "glass-mr-1" }), "Stars"] }), jsxs("label", { className: "glass-text-sm", children: [jsx("input", { type: "checkbox", checked: showSolarWind, onChange: e => {}, className: "glass-mr-1" }), "Solar Wind"] }), jsxs("label", { className: "glass-text-sm", children: [jsx("input", { type: "checkbox", checked: realTimeMode, onChange: e => {}, className: "glass-mr-1" }), "Real-time"] })] })] }); }; return jsx(OptimizedGlassCore, { ref: ref, id: auroraDisplayId, elevation: "level1", intensity: "subtle", depth: 1, tint: "neutral", border: "subtle", className: cn("glass-aurora-display relative glass-radius-lg glass-glass-backdrop-blur-md border border-border/20", className), ...props, children: jsxs(MotionFramer, { preset: isMotionSafe && respectMotionPreference ? "fadeIn" : "none", className: "glass-flex glass-flex-col glass-gap-4 glass-p-4", children: [renderControls(), jsx("div", { className: 'relative', children: jsx("canvas", { ref: canvasRef, width: width, height: height, className: "glass-border glass-border-glass-border/20 glass-radius-md glass-surface-dark glass-contrast-guard", style: { width, height } }) })] }) }); }); GlassAuroraDisplay.displayName = "GlassAuroraDisplay"; export { GlassAuroraDisplay, GlassAuroraDisplay as default }; //# sourceMappingURL=GlassAuroraDisplay.js.map