UNPKG

aura-glass

Version:

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

634 lines (626 loc) 21.6 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { forwardRef, useState, useRef, useEffect, useCallback } 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 GlassHologram = /*#__PURE__*/forwardRef(({ layers, projection = { angle: 0, distance: 100, height: 200, tilt: 0 }, intensity = "medium", colorScheme = "cyan", ambientLight = 0.3, autoRotate = false, rotationSpeed = 1, interactive = true, size = "md", background = "grid", scanLines = true, glitchEnabled = false, chromaticAberration = true, noiseOverlay = true, depthOfField = true, onLayerSelect, onProjectionChange, customShaders = [], loading = false, error, showControls = true, debug = false, respectMotionPreference = true, className, ...props }, ref) => { const { prefersReducedMotion } = useMotionPreferenceContext(); const { play, feedback } = useGlassSound(); const hologramId = useA11yId("glass-hologram"); const [currentProjection, setCurrentProjection] = useState(projection); const [selectedLayer, setSelectedLayer] = useState(null); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); const [isHovering, setIsHovering] = useState(false); const [animationTime, setAnimationTime] = useState(0); const containerRef = useRef(null); const hologramRef = useRef(null); const animationFrameRef = useRef(); // Size configurations const sizeConfig = { sm: { width: 200, height: 150, scale: 0.8 }, md: { width: 300, height: 225, scale: 1 }, lg: { width: 400, height: 300, scale: 1.2 }, xl: { width: 500, height: 375, scale: 1.5 } }; const config = sizeConfig[size]; // Color scheme configurations const colorSchemes = { blue: { primary: "var(--glass-color-primary)", secondary: "#1E40AF", accent: "var(--glass-color-primary-light)" }, green: { primary: "var(--glass-color-success)", secondary: "#047857", accent: "var(--glass-color-success-light)" }, red: { primary: "var(--glass-color-danger)", secondary: "#B91C1C", accent: "var(--glass-color-danger-light)" }, purple: { primary: "#8B5CF6", secondary: "#6D28D9", accent: "#A78BFA" }, cyan: { primary: "#06B6D4", secondary: "#0891B2", accent: "#22D3EE" }, white: { primary: "var(--glass-white)", secondary: "var(--glass-gray-200)", accent: "var(--glass-gray-100)" }, rainbow: { primary: "#FF0080", secondary: "#00FF80", accent: "#8000FF" } }; const colors = colorSchemes[colorScheme]; // Animation loop useEffect(() => { const animate = () => { setAnimationTime(prev => prev + 0.016); // ~60fps if (autoRotate) { setCurrentProjection(prev => ({ ...prev, angle: (prev.angle + rotationSpeed * 0.5) % 360 })); } animationFrameRef.current = requestAnimationFrame(animate); }; if (!prefersReducedMotion && !loading) { animate(); } return () => { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } }; }, [prefersReducedMotion, loading, autoRotate, rotationSpeed]); // Handle mouse movement for interactive projection const handleMouseMove = useCallback(event => { if (!interactive || !containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); const x = ((event.clientX - rect.left) / rect.width - 0.5) * 2; const y = ((event.clientY - rect.top) / rect.height - 0.5) * 2; setMousePosition({ x, y }); if (interactive) { const newProjection = { ...currentProjection, angle: currentProjection.angle + x * 10, tilt: currentProjection.tilt + y * 5 }; setCurrentProjection(newProjection); onProjectionChange?.(newProjection); } }, [interactive, currentProjection, onProjectionChange]); // Calculate layer transform const getLayerTransform = useCallback(layer => { let transform = ""; // Base positioning const { x, y, z } = layer.position; const { x: scaleX, y: scaleY, z: scaleZ } = layer.scale; const { x: rotX, y: rotY, z: rotZ } = layer.rotation; // Animation transforms let animX = x, animY = y, animZ = z; let animRotX = rotX, animRotY = rotY, animRotZ = rotZ; let animScale = 1; if (layer.animation?.enabled) { const { type, speed, amplitude } = layer.animation; const time = animationTime * speed; switch (type) { case "float": animY += Math.sin(time) * amplitude; break; case "rotate": animRotY += time * 50; break; case "pulse": animScale = 1 + Math.sin(time) * amplitude * 0.2; break; case "wave": animX += Math.sin(time) * amplitude; animZ += Math.cos(time) * amplitude; break; case "spiral": animX += Math.sin(time) * amplitude; animY += Math.cos(time) * amplitude; animRotZ += time * 30; break; } } // Projection-based transforms const perspective = 800; const projectionScale = perspective / (perspective + layer.depth); transform += `perspective(${perspective}px) `; transform += `translate3d(${animX}px, ${animY}px, ${animZ}px) `; transform += `rotateX(${animRotX + currentProjection.tilt}deg) `; transform += `rotateY(${animRotY + currentProjection.angle}deg) `; transform += `rotateZ(${animRotZ}deg) `; transform += `scale3d(${scaleX * animScale * projectionScale}, ${scaleY * animScale * projectionScale}, ${scaleZ * animScale}) `; return transform; }, [animationTime, currentProjection]); // Calculate layer style const getLayerStyle = useCallback(layer => { const style = { position: "absolute", top: "50%", left: "50%", transform: getLayerTransform(layer), transformOrigin: "center center", opacity: layer.opacity, zIndex: Math.round(100 - layer.depth), pointerEvents: selectedLayer === layer.id ? "auto" : "none" }; // Holographic effects if (layer.holographic?.chromatic && chromaticAberration) { style.filter = `drop-shadow(2px 0 0 ${colors.primary}40) drop-shadow(-2px 0 0 ${colors.secondary}40)`; } if (layer.holographic?.interference) { style.background = `linear-gradient(90deg, transparent 49%, ${colors.accent}20 50%, transparent 51%)`; style.backgroundSize = "2px 100%"; style.animation = !prefersReducedMotion ? "interference 2s linear infinite" : undefined; } // Glitch effects if (layer.glitch?.enabled && glitchEnabled) { const glitchIntensity = layer.glitch.intensity; const offset = Math.random() * glitchIntensity; style.transform += ` translateX(${offset}px)`; if (Math.random() < layer.glitch.frequency) { style.filter = (style.filter || "") + ` hue-rotate(${Math.random() * 360}deg)`; } } return style; }, [getLayerTransform, selectedLayer, colors, chromaticAberration, glitchEnabled, prefersReducedMotion]); // Handle layer click const handleLayerClick = useCallback(layer => { setSelectedLayer(layer.id === selectedLayer ? null : layer.id); onLayerSelect?.(layer); feedback("tap"); }, [selectedLayer, onLayerSelect, feedback]); // Render layer content const renderLayerContent = useCallback(layer => { switch (layer.type) { case "text": return jsx("div", { className: cn("text-center font-mono whitespace-nowrap", "select-none pointer-events-none"), style: { color: colors.primary, textShadow: `0 0 10px ${colors.primary}80`, fontSize: `${config.scale}rem` }, children: layer.content }); case "image": return jsx("img", { src: layer.source, alt: layer.id, className: 'max-w-full max-h-full object-contain select-none', style: { filter: `drop-shadow(0 0 20px ${colors.primary}60)` }, draggable: false }); case "video": return jsx("video", { src: layer.source, className: 'max-w-full max-h-full object-contain', style: { filter: `drop-shadow(0 0 20px ${colors.primary}60)` }, autoPlay: true, muted: true, loop: true, playsInline: true }); case "shape": return jsx("div", { className: 'w-16 h-16 glass-border-2 glass-radius-lg', style: { borderColor: colors.primary, backgroundColor: `${colors.primary}20`, boxShadow: `0 0 20px ${colors.primary}60, inset 0 0 10px ${colors.primary}40` } }); case "particle": return jsx("div", { className: 'relative w-20 h-20', children: Array.from({ length: 8 }, (_, i) => jsx("div", { className: 'absolute w-1 h-1 glass-radius-full', style: { backgroundColor: colors.accent, boxShadow: `0 0 4px ${colors.accent}`, left: `${Math.cos(i / 8 * Math.PI * 2) * 30 + 40}px`, top: `${Math.sin(i / 8 * Math.PI * 2) * 30 + 40}px`, animation: !prefersReducedMotion ? `particle-orbit ${2 + i * 0.2}s linear infinite` : undefined } }, i)) }); default: return layer.content; } }, [colors, config.scale, prefersReducedMotion]); // Render background pattern const renderBackground = useCallback(() => { switch (background) { case "grid": return jsx("div", { className: 'absolute inset-0 opacity-20', style: { backgroundImage: ` linear-gradient(${colors.primary}40 1px, transparent 1px), linear-gradient(90deg, ${colors.primary}40 1px, transparent 1px) `, backgroundSize: "20px 20px" } }); case "circuit": return jsx("div", { className: 'absolute inset-0 opacity-10', children: jsx("svg", { className: "glass-w-full glass-h-full", children: Array.from({ length: 10 }, (_, i) => jsxs("g", { children: [jsx("path", { d: `M${i * 30} 0 L${i * 30} ${config.height} M0 ${i * 25} L${config.width} ${i * 25}`, stroke: colors.primary, strokeWidth: "0.5", fill: "none" }), jsx("circle", { cx: i * 30, cy: i * 25, r: "2", fill: colors.accent })] }, i)) }) }); case "matrix": return jsx("div", { className: 'absolute inset-0 opacity-15 font-mono glass-text-xs overflow-hidden', style: { color: colors.primary }, children: Array.from({ length: 20 }, (_, i) => jsx("div", { className: 'absolute whitespace-nowrap animate-pulse', style: { left: `${i * 5}%`, top: `${Math.random() * 100}%`, animationDelay: `${Math.random() * 2}s` }, children: Math.random().toString(36).substring(7) }, i)) }); case "particles": return jsx("div", { className: 'absolute inset-0', children: Array.from({ length: 30 }, (_, i) => jsx("div", { className: 'absolute w-1 h-1 glass-radius-full opacity-30', style: { backgroundColor: colors.accent, left: `${Math.random() * 100}%`, top: `${Math.random() * 100}%`, animation: !prefersReducedMotion ? `float ${3 + Math.random() * 2}s ease-in-out infinite alternate` : undefined, animationDelay: `${Math.random() * 2}s` } }, i)) }); default: return null; } }, [background, colors, config, prefersReducedMotion]); // Render scan lines overlay const renderScanLines = () => { if (!scanLines) return null; return jsx("div", { className: 'absolute inset-0 pointer-events-none opacity-20', children: jsx("div", { className: "glass-w-full glass-h-full", ref: el => { if (!el) return; el.style.background = `repeating-linear-gradient(0deg, transparent, transparent 2px, ${colors.primary}40 2px, ${colors.primary}40 4px)`; el.style.animation = !prefersReducedMotion ? "scan 2s linear infinite" : ""; } }) }); }; // Render noise overlay const renderNoiseOverlay = () => { if (!noiseOverlay) return null; return jsx("div", { className: 'absolute inset-0 pointer-events-none opacity-10', ref: el => { if (!el) return; el.style.background = "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E\")"; el.style.animation = !prefersReducedMotion ? "noise 0.2s steps(10) infinite" : ""; } }); }; return jsxs(OptimizedGlassCore, { ref: ref, id: hologramId, elevation: "level1", intensity: "medium", depth: 1, tint: "neutral", border: "subtle", className: cn("glass-hologram relative glass-radius-lg glass-backdrop-blur-md border border-border/20 overflow-hidden", className), style: { width: config.width, height: config.height, background: `radial-gradient(circle, ${colors.primary}10 0%, transparent 70%)` }, onMouseMove: handleMouseMove, onMouseEnter: () => setIsHovering(true), onMouseLeave: () => setIsHovering(false), ...props, children: [jsxs(MotionFramer, { preset: !prefersReducedMotion && respectMotionPreference ? "fadeIn" : "none", className: 'relative glass-w-full glass-h-full', children: [renderBackground(), loading && jsx("div", { className: 'absolute inset-0 glass-flex glass-items-center glass-justify-center', children: jsxs("div", { className: "glass-flex glass-flex-col glass-items-center glass-gap-4", children: [jsx("div", { className: 'w-12 h-12 glass-border-4 glass-border-t-transparent glass-radius-full animate-spin', style: { borderColor: colors.primary, borderTopColor: "transparent" } }), jsx("div", { className: "glass-text-sm", style: { color: colors.primary }, children: "Initializing Hologram..." })] }) }), error && jsx("div", { className: 'absolute inset-0 glass-flex glass-items-center glass-justify-center', children: jsxs("div", { className: 'text-center', children: [jsx("div", { className: 'glass-text-2xl mb-2', children: "\u26A0\uFE0F" }), jsx("div", { className: "glass-text-sm", style: { color: colors.primary }, children: error })] }) }), !loading && !error && jsx("div", { ref: hologramRef, className: 'relative glass-w-full glass-h-full', style: { transformStyle: "preserve-3d", perspective: "800px" }, children: layers.map((layer, index) => jsx(MotionFramer, { preset: !prefersReducedMotion && respectMotionPreference ? "scaleIn" : "none", delay: index * 100, children: jsx("div", { className: cn("hologram-layer cursor-pointer transition-all duration-200", selectedLayer === layer.id && "ring-2 ring-offset-2", layer.holographic?.scanlines && "scanning"), style: { ...getLayerStyle(layer), "--ring-color": colors.accent }, onClick: () => handleLayerClick(layer), children: renderLayerContent(layer) }) }, layer.id)) }), renderScanLines(), renderNoiseOverlay(), showControls && !loading && !error && jsx("div", { className: 'absolute bottom-4 left-1/2 transform -translate-x-1/2', children: jsxs(OptimizedGlassCore, { elevation: "level3", intensity: "strong", depth: 2, tint: "neutral", border: "subtle", className: "glass-flex glass-items-center glass-gap-2 glass-px-4 glass-py-2 glass-radius-lg glass-glass-backdrop-blur-md glass-border glass-border-glass-border/20 glass-contrast-guard", children: [jsx("button", { onClick: () => setCurrentProjection(prev => ({ ...prev, angle: (prev.angle + 90) % 360 })), className: 'glass-p-1 glass-radius-md hover:glass-surface-overlay transition-all', title: "Rotate", style: { color: colors.primary }, children: "\uD83D\uDD04" }), jsx("button", { onClick: () => setCurrentProjection(prev => ({ ...prev, distance: prev.distance === 100 ? 150 : 100 })), className: 'glass-p-1 glass-radius-md hover:glass-surface-overlay transition-all', title: "Zoom", style: { color: colors.primary }, children: "\uD83D\uDD0D" }), jsxs("div", { className: "glass-text-xs", style: { color: colors.primary }, children: ["Layers: ", layers.length] })] }) }), debug && !loading && !error && jsx(OptimizedGlassCore, { elevation: "level2", intensity: "medium", depth: 1, tint: "neutral", border: "subtle", className: 'absolute top-4 left-4 glass-p-3 glass-radius-lg glass-glass-backdrop-blur-md glass-border glass-border-glass-border/20 glass-contrast-guard', children: jsxs("div", { className: 'glass-text-xs font-mono glass-gap-1', style: { color: colors.primary }, children: [jsxs("div", { children: ["Angle: ", currentProjection.angle.toFixed(1), "\u00B0"] }), jsxs("div", { children: ["Tilt: ", currentProjection.tilt.toFixed(1), "\u00B0"] }), jsxs("div", { children: ["Distance: ", currentProjection.distance] }), jsxs("div", { children: ["Layers: ", layers.length] }), jsxs("div", { children: ["Selected: ", selectedLayer || "None"] }), jsxs("div", { children: ["Interactive: ", interactive ? "Yes" : "No"] }), jsxs("div", { children: ["Auto-rotate: ", autoRotate ? "Yes" : "No"] })] }) }), interactive && isHovering && jsx("div", { className: 'absolute top-4 right-4', children: jsx("div", { className: 'w-3 h-3 glass-radius-full animate-pulse', style: { backgroundColor: colors.accent } }) })] }), jsx("style", { children: ` @keyframes interference { 0%, 100% { background-position: 0 0; } 50% { background-position: 100% 0; } } @keyframes scan { 0% { transform: translateY(-100%); } 100% { transform: translateY(100vh); } } @keyframes noise { 0%, 100% { opacity: 0.1; } 50% { opacity: 0.2; } } @keyframes particle-orbit { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes float { from { transform: translateY(0px); } to { transform: translateY(-10px); } } .scanning::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: ${colors.primary}; animation: scan 2s linear infinite; } ` })] }); }); GlassHologram.displayName = "GlassHologram"; export { GlassHologram, GlassHologram as default }; //# sourceMappingURL=GlassHologram.js.map