This is a submission for
There is no backend. No npm. No build step. Just three files (index.html, style.css, app.js), a Python static server, and raw browser APIs.
Demo
/
This is a submission for
There is no backend. No npm. No build step. Just three files (index.html, style.css, app.js), a Python static server, and raw browser APIs.
/
The engine operates as a pipeline with three distinct layers:
[User Input] → [Gemini Roast Generator] → [ElevenLabs Voice Synth] → [Web Audio FFT] → [GLSL Shader]
Layer 1 generates the text. Layer 2 gives it a voice. Layer 3 makes the voice visible.
When you click "ENGAGE BATTLE ENGINE," the app fires off alternating prompts to gemini-3.1-flash-lite:
async function generateBattle(f1, f2) {
const turns = [
{ attacker: f1, defender: f2, side: 'left' },
{ attacker: f2, defender: f1, side: 'right' },
{ attacker: f1, defender: f2, side: 'left' },
{ attacker: f2, defender: f1, side: 'right' }
];
for(let i=0; i<turns.length; i++) {
if(!isBattling) break;
const turn = turns[i];
const prompt = `You are ${turn.attacker}. Write a single, highly
aggressive, funny, and punchy 1-2 sentence roast insulting
${turn.defender}. Use VERY SIMPLE, EASY TO UNDERSTAND language.
Do not include quotes, hashtags, or emojis. Just the raw dialogue.
Speak in: ${preferredLang}.`;
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-lite:generateContent?key=${geminiKey}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
systemInstruction: {
parts: [{ text: "You are a roast character. Output ONLY the raw roast text. No formatting." }]
}
})
});
const data = await response.json();
const roastText = data.candidates[0].content.parts[0].text
.replace(/["\n]/g, '').trim();
roastQueue.push({
attacker: turn.attacker,
side: turn.side,
text: roastText,
dmg: Math.floor(Math.random() * 21) + 40,
voice: turn.side === 'left' ? VOICE_LEFT : VOICE_RIGHT
});
}
}
Key design decision: We use gemini-3.1-flash-lite specifically because latency matters more than depth here. The roasts need to land fast. The systemInstruction enforces raw text output — no markdown, no emoji, no quotes — so the pipeline never chokes on formatting artifacts.
Each generated roast is immediately piped to ElevenLabs' eleven_v3 model with character-specific voice IDs:
function playTurn(turn) {
return new Promise(async (resolve) => {
// Fetch synthesized audio from ElevenLabs
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${turn.voice}?output_format=mp3_44100_128`,
{
method: 'POST',
headers: { 'xi-api-key': elevenKey, 'Content-Type': 'application/json' },
body: JSON.stringify({
text: turn.text,
model_id: "eleven_v3",
voice_settings: { stability: 0.5, similarity_boost: 0.75 }
})
});
// Decode directly into Web Audio API buffer
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// Route through analyser node for FFT data extraction
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(analyser); // ← FFT tap point
analyser.connect(audioContext.destination);
source.start(0);
});
}
The critical routing decision: instead of playing the audio directly to audioContext.destination, we insert an AnalyserNode in between. This gives us access to real-time frequency data (getByteFrequencyData) without affecting playback quality. This is the bridge between Layer 2 and Layer 3.
This is where the magic lives. A full-viewport PlaneGeometry is rendered via Three.js with a custom ShaderMaterial. The fragment shader generates an organic, flowing liquid wave inspired by Google's Gemini Live interface:
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
// Multi-octave noise generates organic wave forms
float n1 = noise(vec2(uv.x * 2.5 + u_time * 0.6, u_time * 0.4));
float n2 = noise(vec2(uv.x * 4.5 - u_time * 0.4, u_time * 0.3));
// Audio volumes directly modulate wave height
float leftHeight = u_leftVolume * 0.28;
float rightHeight = u_rightVolume * 0.28;
float waveHeight = 0.15
+ n1 * (0.12 + leftHeight)
+ n2 * (0.08 + rightHeight);
// Smooth, glowing boundary (no harsh edges)
float alpha = smoothstep(-0.35, 0.05, waveHeight - uv.y);
// Premium gradient: Sky Blue → Violet → Rose Pink
vec3 colorLeft = vec3(0.02, 0.42, 0.98);
vec3 colorMid = vec3(0.48, 0.12, 0.94);
vec3 colorRight = vec3(0.96, 0.05, 0.42);
vec3 gradColor = mix(colorLeft, colorMid, smoothstep(0.1, 0.5, uv.x));
gradColor = mix(gradColor, colorRight, smoothstep(0.5, 0.9, uv.x));
// Brightness surges with voice volume
float intensity = 1.0 + (u_leftVolume + u_rightVolume) * 1.5;
vec3 bgColor = vec3(0.004, 0.004, 0.008) * (1.0 - uv.y);
gl_FragColor = vec4(mix(bgColor, gradColor * intensity, alpha), 1.0);
}
And the JavaScript render loop that feeds it live audio data every frame:
function animate() {
requestAnimationFrame(animate);
uniforms.u_time.value = clock.getElapsedTime();
if(analyser && isBattling) {
const dataArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(dataArray);
// Extract bass frequencies (bins 0-19) for organic pulsing
let sum = 0;
for(let i = 0; i < 20; i++) sum += dataArray[i];
let normalized = (sum / 20.0) / 255.0;
// Lerp prevents jarring visual jumps between frames
if (currentSpeakerSide === 'left') {
uniforms.u_leftVolume.value = THREE.MathUtils.lerp(
uniforms.u_leftVolume.value, normalized, 0.2);
uniforms.u_rightVolume.value = THREE.MathUtils.lerp(
uniforms.u_rightVolume.value, 0, 0.1);
} else if (currentSpeakerSide === 'right') {
uniforms.u_rightVolume.value = THREE.MathUtils.lerp(
uniforms.u_rightVolume.value, normalized, 0.2);
uniforms.u_leftVolume.value = THREE.MathUtils.lerp(
uniforms.u_leftVolume.value, 0, 0.1);
}
}
renderer.render(scene, camera);
}
The lerp (linear interpolation) on the volume values is essential — without it, the wave would jitter violently between frames. The 0.2 attack / 0.1 decay rates create a smooth, organic breathing effect that feels alive.
The visual language is deliberately anti-glassmorphism:
-45DMG) that appear post-speechThe entire battle runs on a producer-consumer queue pattern:
generateBattle() → pushes roasts to roastQueue[]
playbackWorker() → shifts from roastQueue[], plays each turn sequentially
generateBattle() runs ahead, pre-fetching roasts from Gemini while the current turn is still playing. This hides API latency behind the ElevenLabs audio playback window. The user never waits.
gemini-3.1-flash-lite generates all roast dialogue procedurally with zero templates.eleven_v3 synthesizes distinct character voices routed through Web Audio for real-time FFT-driven shader visuals.| Layer | Technology |
|---|---|
| Rendering | Three.js r128 (CDN) |
| Animation | GSAP 3.12.2 (CDN) |
| AI Text | Google Gemini gemini-3.1-flash-lite |
| AI Voice | ElevenLabs eleven_v3 |
| Audio Analysis | Native Web Audio API |
| GPU Shaders | Custom GLSL Fragment Shader |
| Build System | None. python -m http.server 8000 |
Ähnliche Beiträge
Thematisch verwandte Begriffe: Fandom, Fire, GPUAccelerated, AIPowered · 6 Treffer
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.
Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.
SOCIAL SHARE CARD GENERATOR