Files
RepoDructor/components/AuroraBackground.client.vue
2025-08-10 00:45:59 -06:00

361 lines
7.7 KiB
Vue

<template>
<div class="aurora-background">
<!-- Main Aurora Orbs -->
<div
v-for="(orb, index) in auroraOrbs"
:key="`orb-${index}`"
class="aurora-orb"
:class="`orb-${index + 1}`"
:style="orbStyle(orb, index)"
></div>
<!-- Interactive Orbs (respond to music) -->
<div
v-for="(orb, index) in interactiveOrbs"
:key="`interactive-${index}`"
class="interactive-orb"
:style="interactiveOrbStyle(orb, index)"
:class="{ pulsing: isPlaying }"
></div>
<!-- Particle System -->
<div class="particles">
<div
v-for="(particle, index) in particles"
:key="`particle-${index}`"
class="particle"
:style="particleStyle(particle, index)"
></div>
</div>
<!-- Gradient Overlay -->
<div class="gradient-overlay" :class="{ active: isPlaying }"></div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
const props = defineProps({
isPlaying: {
type: Boolean,
default: false
},
currentTrack: {
type: Object,
default: null
}
})
// Aurora orbs configuration
const auroraOrbs = ref([
{
colors: ['var(--aurora-1)', 'var(--aurora-2)'],
size: 300,
position: { top: '10%', left: '10%' },
duration: 8,
delay: 0
},
{
colors: ['var(--aurora-3)', 'var(--aurora-4)'],
size: 250,
position: { top: '60%', right: '20%' },
duration: 10,
delay: 2
},
{
colors: ['var(--aurora-5)', 'var(--aurora-1)'],
size: 200,
position: { bottom: '20%', left: '50%' },
duration: 12,
delay: 4
},
{
colors: ['var(--aurora-2)', 'var(--aurora-5)'],
size: 180,
position: { top: '30%', right: '10%' },
duration: 9,
delay: 6
}
])
// Interactive orbs that respond to music
const interactiveOrbs = ref([
{
colors: ['var(--accent-primary)', 'var(--accent-secondary)'],
size: 120,
position: { top: '20%', left: '70%' },
intensity: 0.5
},
{
colors: ['var(--aurora-3)', 'var(--aurora-5)'],
size: 90,
position: { bottom: '30%', left: '20%' },
intensity: 0.3
},
{
colors: ['var(--aurora-1)', 'var(--aurora-4)'],
size: 100,
position: { top: '70%', right: '30%' },
intensity: 0.4
}
])
// Floating particles
const particles = ref([])
// Animation frame reference
let animationFrame = null
// Generate particles
const generateParticles = () => {
particles.value = Array.from({ length: 20 }, (_, index) => ({
size: Math.random() * 4 + 1,
position: {
left: Math.random() * 100 + '%',
top: Math.random() * 100 + '%'
},
delay: Math.random() * 10,
duration: Math.random() * 20 + 15,
opacity: Math.random() * 0.3 + 0.1
}))
}
// Compute styles for orbs
const orbStyle = (orb, index) => ({
'--orb-size': orb.size + 'px',
'--animation-duration': orb.duration + 's',
'--animation-delay': orb.delay + 's',
'--color-1': orb.colors[0],
'--color-2': orb.colors[1],
...orb.position
})
const interactiveOrbStyle = (orb, index) => ({
'--orb-size': orb.size + 'px',
'--color-1': orb.colors[0],
'--color-2': orb.colors[1],
'--intensity': orb.intensity,
...orb.position
})
const particleStyle = (particle, index) => ({
'--particle-size': particle.size + 'px',
'--animation-delay': particle.delay + 's',
'--animation-duration': particle.duration + 's',
'--particle-opacity': particle.opacity,
...particle.position
})
// Watch for track changes to trigger special effects
watch(() => props.currentTrack, (newTrack, oldTrack) => {
if (newTrack && newTrack !== oldTrack) {
triggerTrackChangeEffect()
}
})
const triggerTrackChangeEffect = () => {
// Create a burst effect when track changes
const burstOrb = document.createElement('div')
burstOrb.className = 'track-change-burst'
burstOrb.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
width: 50px;
height: 50px;
background: radial-gradient(circle, var(--accent-primary), transparent);
border-radius: 50%;
transform: translate(-50%, -50%);
animation: burst 1s ease-out forwards;
pointer-events: none;
z-index: -1;
`
document.body.appendChild(burstOrb)
setTimeout(() => {
document.body.removeChild(burstOrb)
}, 1000)
}
onMounted(() => {
generateParticles()
// Add burst animation keyframes to document
const style = document.createElement('style')
style.textContent = `
@keyframes burst {
0% { transform: translate(-50%, -50%) scale(0); opacity: 1; }
100% { transform: translate(-50%, -50%) scale(20); opacity: 0; }
}
`
document.head.appendChild(style)
})
</script>
<style scoped>
.aurora-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0; /* Render above body background but below main content */
overflow: hidden;
pointer-events: none;
}
.aurora-orb {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.6;
background: linear-gradient(45deg, var(--color-1), var(--color-2));
width: var(--orb-size);
height: var(--orb-size);
animation: float var(--animation-duration) ease-in-out infinite;
animation-delay: var(--animation-delay);
}
.interactive-orb {
position: absolute;
border-radius: 50%;
filter: blur(60px);
opacity: calc(var(--intensity) * 0.8);
background: linear-gradient(45deg, var(--color-1), var(--color-2));
width: var(--orb-size);
height: var(--orb-size);
transition: all 0.5s ease;
animation: gentle-float 6s ease-in-out infinite;
}
.interactive-orb.pulsing {
opacity: calc(var(--intensity) * 1.2);
filter: blur(40px);
animation: music-pulse 2s ease-in-out infinite;
}
.particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.particle {
position: absolute;
width: var(--particle-size);
height: var(--particle-size);
background: radial-gradient(circle, var(--accent-primary), transparent);
border-radius: 50%;
opacity: var(--particle-opacity);
animation: float-particle var(--animation-duration) linear infinite;
animation-delay: var(--animation-delay);
}
.gradient-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(
ellipse at center,
transparent 0%,
rgba(59, 130, 246, 0.05) 50%,
transparent 100%
);
transition: opacity 0.5s ease;
opacity: 0;
}
.gradient-overlay.active {
opacity: 1;
}
/* Animations */
@keyframes float {
0%, 100% {
transform: translate(0, 0) scale(1);
}
33% {
transform: translate(30px, -30px) scale(1.1);
}
66% {
transform: translate(-20px, 20px) scale(0.9);
}
}
@keyframes gentle-float {
0%, 100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(15px, -15px) scale(1.05);
}
}
@keyframes music-pulse {
0%, 100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(10px, -10px) scale(1.2);
}
50% {
transform: translate(-5px, 5px) scale(0.8);
}
75% {
transform: translate(8px, -8px) scale(1.1);
}
}
@keyframes float-particle {
0% {
transform: translateY(100vh) rotate(0deg);
}
100% {
transform: translateY(-10vh) rotate(360deg);
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.aurora-orb {
filter: blur(60px);
}
.interactive-orb {
filter: blur(40px);
}
.interactive-orb.pulsing {
filter: blur(30px);
}
}
/* Reduce motion for accessibility */
@media (prefers-reduced-motion: reduce) {
.aurora-orb,
.interactive-orb,
.particle {
animation-duration: 20s;
}
.interactive-orb.pulsing {
animation: none;
opacity: calc(var(--intensity) * 0.9);
}
}
/* Performance optimization */
.aurora-orb,
.interactive-orb,
.particle {
will-change: transform;
transform: translateZ(0);
}
</style>