361 lines
7.7 KiB
Vue
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>
|