aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
634 lines (626 loc) • 21.6 kB
JavaScript
'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