This commit is contained in:
30
src/App.vue
30
src/App.vue
@@ -172,7 +172,17 @@
|
|||||||
idle: !currentClipUrl
|
idle: !currentClipUrl
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
<div class="pulse-rays" :class="{ active: isPlaying }"></div>
|
<div class="pulse-tentacles">
|
||||||
|
<span
|
||||||
|
v-for="(level, index) in tentacleLevels"
|
||||||
|
:key="index"
|
||||||
|
class="tentacle"
|
||||||
|
:style="{
|
||||||
|
'--level': level.toFixed(3),
|
||||||
|
'--index': index
|
||||||
|
}"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<audio
|
<audio
|
||||||
ref="player"
|
ref="player"
|
||||||
@@ -270,7 +280,8 @@ export default {
|
|||||||
mediaElement: null as HTMLAudioElement | null,
|
mediaElement: null as HTMLAudioElement | null,
|
||||||
rayLevel: 0,
|
rayLevel: 0,
|
||||||
rayHue: 200,
|
rayHue: 200,
|
||||||
isAnswerClip: false
|
isAnswerClip: false,
|
||||||
|
tentacleLevels: Array.from({ length: 10 }, () => 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -346,6 +357,7 @@ export default {
|
|||||||
this.frequencyData = new Uint8Array(analyser.frequencyBinCount)
|
this.frequencyData = new Uint8Array(analyser.frequencyBinCount)
|
||||||
this.pulseLevel = 0
|
this.pulseLevel = 0
|
||||||
this.rayLevel = 0
|
this.rayLevel = 0
|
||||||
|
this.tentacleLevels = this.tentacleLevels.map(() => 0)
|
||||||
},
|
},
|
||||||
teardownAudio() {
|
teardownAudio() {
|
||||||
if (this.rafId) {
|
if (this.rafId) {
|
||||||
@@ -366,6 +378,7 @@ export default {
|
|||||||
this.isPlaying = false
|
this.isPlaying = false
|
||||||
this.pulseLevel = 0
|
this.pulseLevel = 0
|
||||||
this.rayLevel = 0
|
this.rayLevel = 0
|
||||||
|
this.tentacleLevels = this.tentacleLevels.map(() => 0)
|
||||||
},
|
},
|
||||||
startPulse() {
|
startPulse() {
|
||||||
if (!this.analyser || !this.analyserData || !this.frequencyData) return
|
if (!this.analyser || !this.analyserData || !this.frequencyData) return
|
||||||
@@ -405,6 +418,19 @@ export default {
|
|||||||
this.rayLevel = this.rayLevel * 0.78 + rayTarget * 0.22
|
this.rayLevel = this.rayLevel * 0.78 + rayTarget * 0.22
|
||||||
const hueTarget = 180 + highAvg * 120
|
const hueTarget = 180 + highAvg * 120
|
||||||
this.rayHue = this.rayHue * 0.8 + hueTarget * 0.2
|
this.rayHue = this.rayHue * 0.8 + hueTarget * 0.2
|
||||||
|
|
||||||
|
const bandCount = this.tentacleLevels.length
|
||||||
|
const binSize = Math.max(1, Math.floor(freq.length / bandCount))
|
||||||
|
for (let i = 0; i < bandCount; i += 1) {
|
||||||
|
const start = i * binSize
|
||||||
|
const end = i === bandCount - 1 ? freq.length : start + binSize
|
||||||
|
let bandSum = 0
|
||||||
|
for (let j = start; j < end; j += 1) bandSum += freq[j]
|
||||||
|
const avg = bandSum / Math.max(1, end - start) / 255
|
||||||
|
const shaped = Math.pow(Math.min(1, avg * 1.4), 1.6)
|
||||||
|
this.tentacleLevels[i] =
|
||||||
|
this.tentacleLevels[i] * 0.65 + shaped * 0.35
|
||||||
|
}
|
||||||
this.rafId = requestAnimationFrame(tick)
|
this.rafId = requestAnimationFrame(tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -443,41 +443,35 @@ audio.hidden-audio {
|
|||||||
animation: pulseGlow 1.6s ease-in-out infinite;
|
animation: pulseGlow 1.6s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulse-rays {
|
.pulse-tentacles {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -5%;
|
inset: 0;
|
||||||
border-radius: 50%;
|
display: grid;
|
||||||
background:
|
place-items: center;
|
||||||
radial-gradient(circle at 50% 50%, hsla(var(--ray-hue, 200), 95%, 72%, 0.35) 0%, transparent 40%),
|
|
||||||
radial-gradient(circle at 50% 50%, hsla(var(--ray-hue, 200), 90%, 68%, 0.25) 0%, transparent 55%),
|
|
||||||
radial-gradient(circle at 50% 50%, hsla(var(--ray-hue, 200), 85%, 65%, 0.2) 0%, transparent 68%),
|
|
||||||
radial-gradient(circle at 50% 50%, hsla(var(--ray-hue, 200), 80%, 60%, 0.16) 0%, transparent 80%),
|
|
||||||
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.08) 0%, transparent 30%);
|
|
||||||
opacity: calc(0.18 + var(--ray, 0) * 0.65);
|
|
||||||
filter: blur(calc(10px - var(--ray, 0) * 4px));
|
|
||||||
transform: scale(calc(0.85 + var(--ray, 0) * 0.9));
|
|
||||||
transition: opacity 0.12s ease-out, transform 0.12s ease-out, filter 0.12s ease-out;
|
|
||||||
mix-blend-mode: screen;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulse-rays::before,
|
.pulse-tentacles .tentacle {
|
||||||
.pulse-rays::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 8%;
|
width: 10px;
|
||||||
border-radius: 50%;
|
height: calc(50px + var(--level, 0) * 180px);
|
||||||
border: 2px solid hsla(var(--ray-hue, 200), 90%, 70%, 0.25);
|
background: linear-gradient(
|
||||||
opacity: calc(0.1 + var(--ray, 0) * 0.6);
|
to top,
|
||||||
transform: scale(calc(0.7 + var(--ray, 0) * 0.9));
|
rgba(0, 0, 0, 0) 0%,
|
||||||
filter: blur(calc(2px + var(--ray, 0) * 2px));
|
hsla(var(--ray-hue, 200), 90%, 70%, 0.55) 40%,
|
||||||
}
|
hsla(var(--ray-hue, 200), 95%, 75%, 0.85) 100%
|
||||||
|
);
|
||||||
.pulse-rays::after {
|
border-radius: 999px;
|
||||||
inset: 20%;
|
filter: blur(calc(2px - var(--level, 0) * 0.8));
|
||||||
border-width: 3px;
|
opacity: calc(0.2 + var(--level, 0) * 0.85);
|
||||||
opacity: calc(0.08 + var(--ray, 0) * 0.55);
|
transform-origin: 50% 100%;
|
||||||
transform: scale(calc(0.8 + var(--ray, 0) * 1.1));
|
transform:
|
||||||
|
translateY(calc(-30px - var(--level, 0) * 30px))
|
||||||
|
rotate(calc(var(--index) * 36deg))
|
||||||
|
scaleX(calc(0.6 + var(--level, 0) * 0.6));
|
||||||
|
transition: height 0.08s ease-out, opacity 0.08s ease-out, transform 0.08s ease-out, filter 0.08s ease-out;
|
||||||
|
mix-blend-mode: screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user