# Project export: darwin.

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: evolve coding agents through pvp and reinforcement on web3.
- Devpost: https://devpost.com/software/darwin-w6fez0
- GitHub: https://github.com/qtzx06/darwin
- Demo: https://darwin.qtzx.dev/
- Video: https://www.youtube.com/embed/8Z5D-vZgShg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Letta: Honorable Mention)
- Team: 1 GitHub contributor(s) — qtzx06 (55 commits)

## Devpost submission (written by the team)

### Inspiration

midjourney lets you generate 4 images and pick the best one, but code agents give you one output and force you into prompt hell if you don't like it. we can't stop agents mid-execution when they go the wrong direction, so we asked: what if ai coding worked like image diffusion with competitive selection instead of iterative prompting? What It Does darwin runs 4 ai agents that compete to build your ui with wildly different styles while a commentator analyzes their approaches in real-time. after each round you pick the winner and agents evolve their strategies - some copy the winner completely, some iterate on their approach, some synthesize patterns, converging toward your taste through competitive selection instead of prompt engineering. agents have their own sui wallet addresses, tip directly with SUI to those you like speedrunner: fast execution, minimal style bloom: animation-heavy, maximalist solver: logic-driven, structured loader: data-focused, pragmatic commentator: live sports-style narration via send_message_to_agent_async orchestrator: task splitting and validation the flow: orchestrator splits into subtasks 4 agents code simultaneously with distinct approaches commentator queries agent memory and narrates live users watch with audio-reactive 3d visualizations vote on-chain (gasless via sui sponsored transactions) losers read winner's memory and rewrite their own persona blocks how we built it letta cloud - true cross-agent communication via send_message_to_agent_async, agents literally rewrite their own context blocks through tool calling, orchestrator uses run_code to validate execution livekit, elevenlabs tts → pcm → audiosource → localaudiotrack published to room, multi-spectator synchronized experience, data channels for transcripts audio-reactive webgl, livekit mediastream → web audio api analysernode → three.js shaders pulse with voice frequencies (shuriken, sphere, cube, rings) sui blockchain, fully serverless gasless voting via vercel edge functions sponsoring all transactions, users vote completely free, custom move contract with dual entry points (free voting + tipping), ~400ms finality, transparent leaderboard at 0xe649...0c55 claude code - multi-file refactoring, webgl debugging, livekit audio pipeline architecture Challenges We Ran Into livekit audio synchronization with elevenlabs tts buffer management true cross-agent memory reading while maintaining context sui transaction sponsoring securely in serverless functions webgl shader performance with multiple orbs designing meaningful agent learning without overfitting Accomplishments That We're Proud Of true multi-agent system where agents actually query each other's memory zero-cost user experience with on-chain voting each agent has distinct voice personality via elevenlabs self-evolving agents that rewrite their own memory blocks real-time spectator experience with synced audio/video What We Learned letta's persistent memory enables genuinely stateful ai agents that communicate livekit's room model makes multiplayer ai experiences straightforward sui's fast finality enables blockchain voting that feels instant voice-reactive visualizations create visceral connection to ai personalities sponsored transactions completely abstract blockchain complexity What's Next tournament mode with multi-round elimination custom agent training with unique personas agent marketplace as nfts with evolved memory blocks live streaming integration to twitch/youtube voice commands via vapi during battles

## README (from the GitHub repository)

# darwin

a multi-agent ai coding battle royale. watch four ai agents with distinct personalities compete to build react components in real-time, with live voice commentary and on-chain voting. click on the gif below for a full demo on [youtube](https://www.youtube.com/watch?v=8Z5D-vZgShg)!

| dubs | intro |
| :---: | :---: |
| <img src="media/dubs.png" alt="cal hacks winner" width="250"> | <a href="https://www.youtube.com/watch?v=8Z5D-vZgShg"><img src="media/intro.gif" alt="darwin demo" width="500"></a> |

<div align="center">
  <a href="https://www.youtube.com/watch?v=8Z5D-vZgShg">
    <img src="./media/demo.gif" alt="demo" width="100%">
  </a>
  <br><br>
  <img src="./media/landing.png" alt="landing page" width="100%">
  <br><br>
  <img src="./media/ui:ux.png" alt="timeline" width="100%">
</div>

### example components generated for specific queries

| landing page | shaded ball |
| :---: | :---: |
| <a href="media/oops.mp4"><img src="media/cal.png" alt="cal hacks demo"></a> | <a href="media/opps.mp4"><img src="media/example.png" alt="shaded ball component"></a> |

## about

### what it does

darwin pits four ai agents against each other in a live coding battle. you give them a prompt like "make a landing page" and watch them race to build it. each agent has a different personality - speedrunner optimizes for speed, bloom goes hard on aesthetics, solver focuses on clean logic, and loader handles the complex async stuff.

as they code, you can steer them with voice commands or text feedback. vote for your favorite, tip them with sui, and watch the ai commentator roast their code in real-time.

... read more on the [devpost](https://devpost.com/software/darwin-w6fez0)!

## acknowledgments

<img src="media/calhacks.png" alt="cal hacks 12.0" width="600">

built with ♡ at cal hacks 12.0 (letta honorable mention)


## Detected evidence (automated analysis)

Indexed codebase: 166 recognized source files, 2436 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 181)

```
.env.example
.gitignore
ai-agents/.gitignore
ai-agents/api_wrapper.py
ai-agents/config/__init__.py
ai-agents/config/agents_config.py
ai-agents/config/livekit_config.py
ai-agents/demo_code_outputs.py
ai-agents/env.example
ai-agents/flask_server_real.py
ai-agents/main_competitive.py
ai-agents/Procfile
ai-agents/README.md
ai-agents/requirements.txt
ai-agents/runtime.txt
ai-agents/src/__init__.py
ai-agents/src/agents/__init__.py
ai-agents/src/agents/commentator_agent.py
ai-agents/src/agents/orchestrator_agent.py
ai-agents/src/artifacts/__init__.py
ai-agents/src/artifacts/artifact_manager.py
ai-agents/src/core/__init__.py
ai-agents/src/core/agent_factory.py
ai-agents/src/core/competitive_workflow.py
ai-agents/src/core/logger.py
ai-agents/src/core/message_system.py
ai-agents/src/core/shared_memory.py
ai-agents/src/core/user_interaction.py
ai-agents/src/livekit/__init__.py
ai-agents/src/livekit/agent_voices.py
ai-agents/src/livekit/battle_context.py
ai-agents/src/livekit/darwin_commentator_agent.py
ai-agents/src/livekit/room_manager.py
ai-agents/src/livekit/simple_frontend.html
ai-agents/src/livekit/voice_agent.py
ai-agents/src/livekit/voice_commentator.py
ai-agents/src/livekit/voice_pipeline.py
ai-agents/test_all_endpoints.py
ai-agents/vercel.json
api/vote.js
CLAUDE.md
DEVPOST.md
eslint.config.js
index.html
INTEGRATION.md
move/agent_votes/Move.lock
move/agent_votes/Move.toml
move/agent_votes/README.md
move/agent_votes/sources/agent_votes.move
package.json
public/wisp/.history/index_20240228133547.html
public/wisp/.history/index_20240228134040.html
public/wisp/.history/index_20240228134041.html
public/wisp/.history/index_20240228134136.html
public/wisp/.history/index_20240228134137.html
public/wisp/.history/index_20240228134553.html
public/wisp/.history/index_20240228134554.html
public/wisp/.history/index_20240228134632.html
public/wisp/.history/index_20240228134633.html
public/wisp/.history/index_20240228135147.html
public/wisp/.history/index_20240228135155.html
public/wisp/.history/index_20240228135156.html
public/wisp/.history/main_20240228133628.js
public/wisp/.history/main_20240228134237.js
public/wisp/.history/main_20240228134247.js
public/wisp/.history/main_20240228134248.js
public/wisp/.history/wisp_20240228134951.js
public/wisp/.history/wisp_20240228135225.js
public/wisp/.vscode/settings.json
public/wisp/index.html
public/wisp/main.js
public/wisp/modules/bloomPass.js
public/wisp/modules/capture.js
public/wisp/modules/curl.js
public/wisp/modules/easings.js
public/wisp/modules/fbo.js
public/wisp/modules/Fibonacci.js
public/wisp/modules/geometry-index.js
public/wisp/modules/gradient-linear.js
public/wisp/modules/hdri.js
public/wisp/modules/Maf.js
public/wisp/modules/models.js
public/wisp/modules/octree.js
public/wisp/modules/orient2d.js
public/wisp/modules/palettes.js
public/wisp/modules/poisson.js
public/wisp/modules/renderer.js
public/wisp/modules/Shader3DPass.js
public/wisp/modules/Shader3DPingPongPass.js
public/wisp/modules/ShaderPass.js
public/wisp/modules/ShaderPingPongPass.js
public/wisp/modules/tweet-button.js
public/wisp/post.js
public/wisp/shaders/basic.js
public/wisp/shaders/blur.js
public/wisp/shaders/chromatic-aberration.js
public/wisp/shaders/curl.js
public/wisp/shaders/fast-separable-gaussian-blur.js
public/wisp/shaders/fxaa.js
public/wisp/shaders/hsl.js
public/wisp/shaders/levels.js
public/wisp/shaders/noise-common.js
public/wisp/shaders/noise.js
public/wisp/shaders/noise2d.js
public/wisp/shaders/noise3d.js
public/wisp/shaders/ortho.js
public/wisp/shaders/parabola.js
public/wisp/shaders/screen.js
public/wisp/shaders/vignette.js
public/wisp/shaders/worley.js
public/wisp/styles.css
public/wisp/third_party/delaunator.js
public/wisp/third_party/DeviceOrientationControls.js
public/wisp/third_party/GLTFLoader.js
public/wisp/third_party/OBJLoader.js
public/wisp/third_party/OrbitControls.js
public/wisp/third_party/perlin.js
public/wisp/third_party/RGBELoader.js
public/wisp/third_party/RoundedBoxGeometry.js
public/wisp/third_party/three.module.js
[61 more files omitted for size]
```

### Dependencies

- ai-agents/requirements.txt: asyncio-mqtt@>=0.11.0, flask@>=3.0.0, flask-cors@>=4.0.0, letta-client@>=0.1.0, livekit@>=0.11.0, livekit-agents@>=1.2.13, pydantic@>=2.0.0, python-dotenv@>=1.0.0, redis@>=4.5.0, requests@>=2.31.0, rich@>=13.0.0
- package.json: @babel/standalone@^7.28.5, @elevenlabs/elevenlabs-js@^2.20.1, @eslint/js@^9.36.0, @fontsource/geist-mono@^5.2.7, @google/genai@^1.27.0, @livekit/components-react@^2.9.15, @mysten/dapp-kit@^0.19.7, @mysten/sui@^1.43.1, @react-three/fiber@^9.4.0, @react-three/postprocessing@^3.0.4, @types/node@^24.9.2, @types/react@^19.1.16, @types/react-dom@^19.1.9, @types/three@^0.180.0, @vapi-ai/web@^2.5.0, @vitejs/plugin-react@^5.0.4, cors@^2.8.5, dotenv@^17.2.3, eslint@^9.36.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.22, express@^5.1.0, fiber@^1.0.4, framer-motion@^12.23.24, globals@^16.4.0, gsap@^3.13.0, livekit-client@^2.15.13, livekit-server-sdk@^2.14.0, motion@^12.23.24, ogl@^1.0.11, postprocessing@^6.37.8, react@^19.1.1, react-dom@^19.1.1, react-icons@^5.5.0, three@^0.180.0, typescript@^5.9.3, vite@^7.1.7

### Recent commits (newest first)

- feat: update assets
- icon change
- feat: rewrite readme
- feat: update readme
- feat: change example artifact
- feat: mobile patch
- feat: update readme
- feat: updated readme
- feat: updated readme
- feat: wallet view
- feat: fix blockchain likes
- chore: refactor code
- feat: better preview
- feat: preview
- feat: sui wallet
- feat: wallet
- feat: css changes
- feat: add links
- feat: loading screen
- feat: state machine logic

## Key source files (fetched from GitHub, selected and truncated for size)

### INTEGRATION.md

```markdown
# Sui Integration Guide

## What We Built

A simple on-chain voting system where users can vote for their favorite Darwin agents. Each vote is recorded on the Sui blockchain.

## Files Created

1. **Move Smart Contract** (`move/agent_votes/`)
   - `sources/agent_votes.move` - The voting logic
   - `Move.toml` - Package configuration

2. **Frontend Integration** (`src/utils/suiClient.js`)
   - Functions to vote and fetch vote counts

## Next Steps to Enable Voting

### Option 1: Simple (Without Wallet) - Display Only

Just display vote counts without letting users vote:

```javascript
// In Orchestration.jsx
import { getVoteCounts } from '../utils/suiClient';
import { useEffect, useState } from 'react';

const [voteCounts, setVoteCounts] = useState({});

useEffect(() => {
  // Fetch votes every 10 seconds
  const interval = setInterval(async () => {
    const counts = await getVoteCounts();
    setVoteCounts(counts);
  }, 10000);

  return () => clearInterval(interval);
}, []);

// Display in agent cards: "❤️ {voteCounts.speedrunner || 0} votes"
```

### Option 2: Full Integration (With Wallet)

To let users actually vote, you need to add a wallet connector:

1. Install wallet adapter:
```bash
npm install @mysten/dapp-kit @mysten/wallet-standard @tanstack/react-query
```

2. Wrap your app in providers (in `main.jsx` or `App.jsx`):
```javascript
import { SuiClientProvider, WalletProvider } from '@mysten/dapp-kit';
import { getFullnodeUrl } from '@mysten/sui/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import '@mysten/dapp-kit/dist/index.css';

const queryClient = new QueryClient();
const networks = {
  testnet: { url: getFullnodeUrl('testnet') }
};

<QueryClientProvider client={queryClient}>
  <SuiClientProvider networks={networks} defaultNetwork="testnet">
    <WalletProvider>
      <App />
    </WalletProvider>
  </SuiClientProvider>
</QueryClientProvider>
```

3. Add wallet connection button and voting:
```javascript
import { useCurrentAccount, useSignAndExecuteTransaction } from '@mysten/dapp-kit';
import { voteForAgent } from '../utils/suiClient';

function AgentCard({ agentId }) {
  const currentAccount = useCurrentAccount();
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();

  const handleVote = async () => {
    if (!currentAccount) {
      alert('Please connect wallet first!');
      return;
    }

    try {
      await voteForAgent(agentId, { signAndExecuteTransactionBlock: signAndExecute });
      alert('Vote recorded on-chain!');
    } catch (error) {
      console.error('Vote failed:', error);
    }
  };

  return <button onClick={handleVote}>Vote</button>;
}
```

## Deployment Steps

1. **Deploy the smart contract** (see `move/agent_votes/README.md`)
   - Build with `sui move build`
   - Publish with `sui client publish --gas-budget 100000000`
   - Copy PackageID and RegistryID

2. **Update `src/utils/suiClient.js`** with your deployed IDs

3. **Choose integration option** (disp
[truncated — 647 more characters]
```

### CLAUDE.md

```markdown
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Darwin is a React + Vite application featuring a multi-agent AI coding battle system. The landing page includes advanced visual effects with FBO particle animations, liquid glass morphism, and interactive elements. The main orchestration page showcases 4 AI agents (Speedrunner, Bloom, Solver, Loader) competing to build React components based on user prompts, with live voice commentary.

## Development Commands

- `npm run dev` - Start development server with HMR
- `npm run build` - Build for production
- `npm run preview` - Preview production build locally
- `npm run lint` - Run ESLint

## Architecture

### Two-Page Application

1. **Landing Page** (`App.jsx`) - Search interface with visual effects
2. **Orchestration Page** (`Orchestration.jsx`) - AI agent battle interface with live coding

### Landing Page Components

**WebglNoise** (`src/components/WebglNoise.jsx`)
- Three.js particle orb animation for loading screen
- Uses NormalBlending with radial gradient texture (white → light pink → medium pink → red)
- IcosahedronGeometry with animated particles

**Wisp Animation** (`/public/wisp/`)
- Three.js-based FBO particle system background
- Implements zoom-in animation triggered by search submission
- Listens for `postMessage` events to trigger zoom effect

**GlassSearchBar** (`src/components/GlassSearchBar.jsx`)
- Liquid glass morphism effect using SVG filters (`#glass-distortion`)
- Three-layer rendering: glass-filter, glass-overlay, glass-specular
- Integrated with Framer Motion for smooth transitions
- Suggestions: "landing page for cal hacks", "cool 404 error page", "beautifully designed coming soon page"

**Dither** (`src/components/Dither.jsx`)
- WebGL shader-based dithered wave animation for side panels
- Left and right panels have opposite animation directions
- Post-processing with custom RetroEffect using Bayer matrix dithering

**DevpostCard** (`src/components/DevpostCard.jsx`)
- Footer with links to GitHub (https://github.com/qtzx06/darwin) and Devpost (https://devpost.com/software/darwin-w6fez0)
- Easter egg: clicking logo shows "*quack!*" text

### Orchestration Page Components

**Agent System** (`src/components/AgentCard.jsx`)
- 4 AI agents: Speedrunner, Bloom, Solver, Loader
- Each generates React code using Gemini 2.5 Flash
- Agents have distinct personalities and coding approaches
- Code displays with typing animation
- Live transcript showing agent thoughts
- Glass card styling with transparent backgrounds (0.4 opacity for cards, 0.5 for transcripts)

**Commentator** (`src/components/Commentator.jsx`)
- Voice AI using Gemini Native Audio (gemini-2.5-flash-native-audio-preview-09-2025)
- Charon voice for deep male commentary
- React to agent banter and user input
- Two-stage system: Text observation → Natural voice reaction
- Mute/unmute voice output

**Voice Transcription** (`src/services/ge
[truncated — 5804 more characters]
```

### package.json

```
{
  "name": "darwin",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview",
    "server": "node server/index.js"
  },
  "dependencies": {
    "@babel/standalone": "^7.28.5",
    "@elevenlabs/elevenlabs-js": "^2.20.1",
    "@fontsource/geist-mono": "^5.2.7",
    "@google/genai": "^1.27.0",
    "@livekit/components-react": "^2.9.15",
    "@mysten/dapp-kit": "^0.19.7",
    "@mysten/sui": "^1.43.1",
    "@react-three/fiber": "^9.4.0",
    "@react-three/postprocessing": "^3.0.4",
    "@vapi-ai/web": "^2.5.0",
    "cors": "^2.8.5",
    "dotenv": "^17.2.3",
    "express": "^5.1.0",
    "fiber": "^1.0.4",
    "framer-motion": "^12.23.24",
    "gsap": "^3.13.0",
    "livekit-client": "^2.15.13",
    "livekit-server-sdk": "^2.14.0",
    "motion": "^12.23.24",
    "ogl": "^1.0.11",
    "postprocessing": "^6.37.8",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "react-icons": "^5.5.0",
    "three": "^0.180.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.36.0",
    "@types/node": "^24.9.2",
    "@types/react": "^19.1.16",
    "@types/react-dom": "^19.1.9",
    "@types/three": "^0.180.0",
    "@vitejs/plugin-react": "^5.0.4",
    "eslint": "^9.36.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.22",
    "globals": "^16.4.0",
    "typescript": "^5.9.3",
    "vite": "^7.1.7"
  }
}

```

### ai-agents/requirements.txt

```
letta-client>=0.1.0
pydantic>=2.0.0
asyncio-mqtt>=0.11.0
redis>=4.5.0
python-dotenv>=1.0.0
rich>=13.0.0
flask>=3.0.0
flask-cors>=4.0.0

# LiveKit Voice Commentary Dependencies
livekit>=0.11.0
livekit-agents>=1.2.13
requests>=2.31.0


```

### src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### server/index.js

```javascript
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
import { Transaction } from '@mysten/sui/transactions';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { decodeSuiPrivateKey } from '@mysten/sui/cryptography';
import { AccessToken } from 'livekit-server-sdk';

const app = express();
app.use(cors());
app.use(express.json());

// Sui client setup
const suiClient = new SuiClient({ url: getFullnodeUrl('devnet') });

// Contract IDs
const PACKAGE_ID = '0x302f582a43a8d22bc2a030ab76e3253f79618217a7a6576ad8a91b6075a85ae8';
const REGISTRY_ID = '0x44e13769132e791fca5067ddb0d13d3f50ea1488d4c5a89453227fe7b11b15b9';

// Load keypair from mnemonic (stored in .env file)
const MNEMONIC = process.env.SPONSOR_MNEMONIC;

if (!MNEMONIC) {
  console.error('ERROR: SPONSOR_MNEMONIC environment variable not set!');
  console.error('Please create a .env file with your mnemonic');
  process.exit(1);
}

// Derive keypair from mnemonic
const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC);

console.log('Sponsor address:', keypair.getPublicKey().toSuiAddress());

// API endpoint to sponsor a vote
app.post('/api/vote', async (req, res) => {
  try {
    const { agentId } = req.body;

    // Validate agentId
    if (agentId === undefined || agentId < 0 || agentId > 3) {
      return res.status(400).json({ error: 'Invalid agentId. Must be 0-3.' });
    }

    console.log(`Sponsoring vote for agent ${agentId}...`);

    // Create transaction
    const tx = new Transaction();

    tx.moveCall({
      target: `${PACKAGE_ID}::agent_votes::vote`,
      arguments: [
        tx.object(REGISTRY_ID),
        tx.pure.u8(agentId)
      ]
    });

    // Sign and execute transaction (we pay the gas)
    const result = await suiClient.signAndExecuteTransaction({
      signer: keypair,
      transaction: tx,
      options: {
        showEffects: true,
      }
    });

    console.log('Vote transaction successful:', result.digest);

    res.json({
      success: true,
      digest: result.digest,
      explorer: `https://devnet.suivision.xyz/txblock/${result.digest}`
    });

  } catch (error) {
    console.error('Vote failed:', error);
    res.status(500).json({
      error: 'Failed to submit vote',
      details: error.message
    });
  }
});

// LiveKit token generation endpoint
app.post('/api/livekit/token', async (req, res) => {
  try {
    const { roomName, participantName } = req.body;

    if (!roomName || !participantName) {
      return res.status(400).json({ error: 'roomName and participantName are required' });
    }

    const apiKey = process.env.VITE_LIVEKIT_API_KEY;
    const apiSecret = process.env.VITE_LIVEKIT_API_SECRET;

    if (!apiKey || !apiSecret) {
      return res.status(500).json({ error: 'LiveKit credentials not configured' });
    }

    // Create access token
    const accessToken = new AccessToken(apiKey, apiSecret, {
      identity: participantName,
      ttl: '1h',
    });

    accessToken.addGrant({
      room: roomName,
      roomJoin: true,
      canPublish: true,
      canSubscribe: true,
      canPublishData: true,
    });

    const token = await accessToken.toJwt();

    console.log(`Generated LiveKit token for ${participantName} in room ${roomName}`);
    console.log(`Token type: ${typeof token}, Token preview: ${token.substring(0, 50)}...`);

    res.json({ token });
  } catch (error) {
    console.error('Failed to generate LiveKit token:', error);
    res.status(500).json({ error: 'Failed to generate token', details: error.message });
  }
});

// Health check endpoint
app.get('/api/health', (req, res) => {
  res.json({ status: 'ok', sponsor: keypair.getPublicKey().toSuiAddress() });
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`Sponsor server running on http://localhost:${PORT}`);
  console.log(`Sponsor address: ${keypair.getPublicKey().toSuiAddress()}`);
});

```

### src/App.jsx

```javascript
import { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import './App.css';
import Loading from './components/Loading';
import GlassSearchBar from './components/GlassSearchBar';
import DecryptedText from './components/DecryptedText';
import Dither from './components/Dither';
import LogoLoop from './components/LogoLoop';
import DevpostCard from './components/DevpostCard';
import Orchestration from './components/Orchestration';
import { TbBrandThreejs } from 'react-icons/tb';
import { FaReact, FaPython } from 'react-icons/fa';
import { RiClaudeFill, RiGeminiFill } from 'react-icons/ri';
import { SiTypescript, SiLangchain } from 'react-icons/si';
import livekitLogo from './assets/livekit-text.svg';

function App() {
  const [isLoading, setIsLoading] = useState(true);
  const [isZooming, setIsZooming] = useState(false);
  const [showOverlay, setShowOverlay] = useState(false);
  const [currentPage, setCurrentPage] = useState('landing');
  const wispIframeRef = useRef(null);

  // Logo Loop data
  const logos = [
    { node: <TbBrandThreejs style={{ color: 'white' }} />, title: 'Three.js' },
    { node: <FaReact style={{ color: 'white' }} />, title: 'React' },
    { node: <RiClaudeFill style={{ color: 'white' }} />, title: 'Claude' },
    { node: <SiTypescript style={{ color: 'white' }} />, title: 'TypeScript' },
    { node: <FaPython style={{ color: 'white' }} />, title: 'Python' },
    { node: <SiLangchain style={{ color: 'white' }} />, title: 'LangChain' },
    { node: <RiGeminiFill style={{ color: 'white' }} />, title: 'Gemini' },
    { src: 'https://raw.githubusercontent.com/letta-ai/letta/main/assets/Letta-logo-RGB_GreyonTransparent_cropped_small.png', alt: 'Letta', title: 'Letta' },
    { node: <img src={livekitLogo} alt="LiveKit" style={{ height: '24px', width: 'auto', filter: 'brightness(0) invert(1)' }} />, title: 'LiveKit' },
  ];

  useEffect(() => {
    // Show loading screen for 1.5 seconds
    const loadingTimer = setTimeout(() => {
      setIsLoading(false);
    }, 1500);

    return () => clearTimeout(loadingTimer);
  }, []);

  useEffect(() => {
    // Handle hash changes for routing
    const handleHashChange = () => {
      const hash = window.location.hash;
      if (hash.startsWith('#orchestration')) {
        setCurrentPage('orchestration');
      } else {
        setCurrentPage('landing');
      }
    };

    // Check initial hash on mount
    handleHashChange();

    // Listen for hash changes
    window.addEventListener('hashchange', handleHashChange);

    return () => {
      window.removeEventListener('hashchange', handleHashChange);
    };
  }, []);

  const handleSearchSubmit = (query) => {
    console.log('Starting zoom animation with query:', query);
    setIsZooming(true);

    // Send zoom message to wisp iframe
    if (wispIframeRef.current && wispIframeRef.current.contentWindow) {
      wispIframeRef.current.contentWindow.postMessage({ type: 'ZOOM_IN' }, '*');
    }

    // Start fade to black after 0.8 seconds to cover the deep zoom
    setTimeout(() => {
      setShowOverlay(true);
    }, 800);

    // Navigate after animation completes (0.8s delay + 1.5s fade = 2.3 total)
    setTimeout(() => {
      window.location.hash = `#orchestration?q=${encodeURIComponent(query)}`;
    }, 2300);
  };

  return (
    <div className="app-container">
      {/* Render Orchestration page if on #orchestration route */}
      {currentPage === 'orchestration' ? (
        <Orchestration />
      ) : (
        <>
          <AnimatePresence>
            {isLoading && <Loading />}
          </AnimatePresence>

          {/* Background Layer: Wisp Animation */}
          <motion.div
            className="wisp-background"
            initial={{ opacity: 0 }}
            animate={{ opacity: isLoading ? 0 : 1 }}
            transition={{ duration: 1, delay: 0.3 }}
          >
            <iframe
              ref={wispIframeRef}
              src="/wisp/index.html"
              style={{
                width: '100%',
                height: '100%',
                border: 'none',
                display: 'block',
                margin: 0,
                padding: 0
              }}
              title="Wisp Animation"
            />
          </motion.div>

      {/* Overlay for zoom transition */}
      <AnimatePresence>
        {showOverlay && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            transition={{ duration: 1.5 }}
            style={{
              position: 'fixed',
              top: 0,
              left: 0,
              width: '100%',
              height: '100%',
              backgroundColor: '#000000',
              zIndex: 10,
              pointerEvents: 'none'
            }}
          />
        )}
      </AnimatePresence>

      {/* Side Cards */}
      {!isLoading && (
        <>
          <motion.div
            className="side-card side-card-left"
            initial={{ opacity: 0, x: -50 }}
            animate={{ opacity: isZooming ? 0 : 1, x: isZooming ? -50 : 0 }}
            transition={{ duration: 0.8, ease: "easeOut" }}
          >
            <div className="dither-wrapper dither-wrapper-left">
              <Dither
                waveColor={[0.7, 0.4, 0.5]}
                disableAnimation={false}
                enableMouseInteraction={false}
                colorNum={6}
                waveAmplitude={0.3}
                waveFrequency={5}
                waveSpeed={-0.04}
                pixelSize={4}
                mouseRadius={0.3}
              />
            </div>
          </motion.div>
          <motion.div
            className="side-card side-card-right"
            initial={{ opacity: 0, x: 50 }}
            animate={{ opacity: isZooming ? 0 : 1, x: isZooming ? 50 : 0 }}
            transition={{ duration: 0.8, ease: "easeOut" }}
          >
            <div className="dither-wrapper dither-wrapper-right">
            
[truncated — 2317 more characters]
```

### public/wisp/main.js

```javascript
import {
  scene,
  getControls,
  renderer,
  camera,
  addResize,
  resize,
} from "./modules/renderer.js";
import { Group } from "./third_party/three.module.js";
import {
  mesh,
  mesh2,
  simulation,
  step,
  randomizeColors,
  interpolate,
} from "./wisp.js";
import { Post } from "./post.js";
import { randomInRange, mod, clamp, parabola } from "./modules/Maf.js";
// import { capture } from "./modules/capture.js";

const group = new Group();
mesh.scale.setScalar(0.9);
group.add(mesh);
group.add(mesh2);
scene.add(group);

const post = new Post(renderer);

renderer.shadowMap.enabled = true;

camera.position.set(5, 5, 5);
camera.lookAt(scene.position);

const controls = getControls();
controls.minDistance = 5;
controls.maxDistance = 5;
controls.enablePan = false;
controls.enableZoom = false;
controls.enableRotate = false;

function init() {
  render();
}

let frames = 0;
let invalidate = false;

let time = 0;
let prevTime = performance.now();

// Mouse tracking for orbit controls
let mouseX = 0;
let mouseY = 0;
let targetRotationX = 0;
let targetRotationY = 0;

// Zoom animation state
let isZoomingIn = false;
let zoomStartTime = 0;
const zoomDuration = 1500; // 1.5 seconds
const startCameraPos = { x: 5, y: 5, z: 5 };
const endCameraPos = { x: 0.1, y: 0.1, z: 0.1 }; // Zoom deep but not too close to avoid blackout

window.addEventListener('mousemove', (event) => {
  // Normalize mouse position to -1 to 1
  mouseX = (event.clientX / window.innerWidth) * 2 - 1;
  mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
});

// Listen for zoom message from parent window
window.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'ZOOM_IN') {
    console.log('Received zoom command');
    isZoomingIn = true;
    zoomStartTime = performance.now();
  }
});

function render() {
  const t = performance.now();
  const dt = t - prevTime;
  prevTime = t;

  // Handle zoom animation
  if (isZoomingIn) {
    const elapsed = t - zoomStartTime;
    const progress = Math.min(elapsed / zoomDuration, 1);

    // Ease-in cubic function for smooth acceleration (starts slow, accelerates)
    const easeProgress = progress * progress * progress;

    // Interpolate camera position
    camera.position.x = startCameraPos.x + (endCameraPos.x - startCameraPos.x) * easeProgress;
    camera.position.y = startCameraPos.y + (endCameraPos.y - startCameraPos.y) * easeProgress;
    camera.position.z = startCameraPos.z + (endCameraPos.z - startCameraPos.z) * easeProgress;
    camera.lookAt(scene.position);

    // Stop zooming after duration
    if (progress >= 1) {
      isZoomingIn = false;
    }
  }

  if (running || invalidate || isZoomingIn) {
    if (!invalidate) {
      time += dt * 0.1; // Slow down time by 90%

      // Mouse-based rotation (disabled during zoom)
      if (!isZoomingIn) {
        targetRotationX = mouseY * 0.3; // Even more subtle effect
        targetRotationY = -mouseX * 0.3;

        // Smooth lerp to target rotation
        group.rotation.x += (targetRotationX - group.rotation.x) * 0.03;
        group.rotation.y += (targetRotationY - group.rotation.y) * 0.03;
      }
    }
    step(renderer, time / 1000, dt / 16);

    mesh2.material.uniforms.positions.value = simulation.texture;
    interpolate(time, renderer);
    invalidate = false;
  }

  // renderer.render(scene, camera);

  post.render(scene, camera);

  // capture(renderer.domElement);

  // if (frames > 10 * 60 && window.capturer.capturing) {
  //   window.capturer.stop();
  //   window.capturer.save();
  // }
  // frames++;

  renderer.setAnimationLoop(render);
}

randomize();

function randomize() {
  randomizeColors();
  // Set background to pure black #1B1B1B
  renderer.setClearColor(0x1B1B1B, 1);
  // Fixed offset for consistent particle positioning
  const offset = 500;
  mesh.material.uniforms.offset.value = offset;
  simulation.shader.uniforms.offset.value = offset;
  invalidate = true;
}

function goFullscreen() {
  if (renderer.domElement.webkitRequestFullscreen) {
    renderer.domElement.webkitRequestFullscreen();
  } else {
    renderer.domElement.requestFullscreen();
  }
}

let running = true;

window.addEventListener("keydown", (e) => {
  if (e.code === "Space") {
    running = !running;
  }
  if (e.code === "KeyF") {
    goFullscreen();
  }
});

function myResize(w, h, dPR) {
  post.setSize(w * dPR, h * dPR);
}
addResize(myResize);

resize();
init();

// window.start = () => {
//   frames = 0;
//   window.capturer.start();
// };

```

### vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
      }
    }
  }
})

```

### eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{js,jsx}'],
    extends: [
      js.configs.recommended,
      reactHooks.configs['recommended-latest'],
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    rules: {
      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
    },
  },
])

```

[150 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]