# Project export: CupVoice

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: UC Berkeley AI Hackathon 2026
- Tagline: CupVoice is a multi-modal accessibility layer for live sports, transforming match events into audio, captions, pattern-first visuals, and Braille-ready updates.
- Devpost: https://devpost.com/software/cupvoice
- GitHub: https://github.com/Abulala1/cupvoice.git
- Video: https://www.youtube.com/embed/T47KknaDHGA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — ella (1 commits)

## Devpost submission (written by the team)

### Overview

🌍

### Inspiration

AI is already helping people understand information in new ways. CupVoice brings that idea into live sports. Live sports are exciting, but they can also be hard to follow because everything happens fast: the movement, the sound, the visuals, the crowd, and the commentary. We built CupVoice around one question: What if the same match could be translated into the format that works best for each fan? CupVoice turns live-style match events into audio commentary, text cards, high-contrast visual cues, calmer interface modes, and Braille-ready output. Instead of giving every fan the same broadcast experience, CupVoice helps each fan follow the game in the way that works best for them. Same match. Different ways to follow it. Every fan included. 🧠

### What it does

CupVoice transforms live-style football match events into multiple accessibility-focused outputs. Instead of showing the same fast-paced broadcast to everyone, our prototype adapts each event to different user needs. A single match event can become: 🎧 Spatial audio commentary for blind and low-vision fans 📝 Dynamic captions and event cards for deaf and hard-of-hearing fans 🎨 High-contrast, pattern-first visuals for color-blind users ⠿ Braille-ready structured text for users who cannot rely on sight or sound 🌙 A calmer, lower-stimulation mode for sensory-sensitive fans For example, a goal is not just shown as a visual highlight. CupVoice can communicate: who scored where the play happened how the ball moved what changed in the score why the moment matters in the match context For Braille-ready output, the same event is compressed into a short structured line such as: 26:14 | ARG GOAL | Messi | right box | low shot | ARG 1-0 FRA This makes the information easier to send to a refreshable Braille display or a Braille simulator. 🏗️

### How we built it

We built CupVoice around a structured live match event pipeline powered by the Claude API and Deepgram API. Instead of trying to process a full sports broadcast video feed during the hackathon, we focused on a more reliable MVP: simulated live match events. Each event contains structured fields such as: time team player event type location score impact match context These structured events are sent to the Claude API, which translates the same play into different accessibility-focused outputs, including beginner-friendly explanations, audio commentary text, caption-ready summaries, and Braille-ready structured lines. For voice output, we use the Deepgram API to convert the generated commentary into natural spoken audio. This allows CupVoice to turn the same match event into a format that can be heard, read, displayed visually, or sent toward Braille-ready hardware. In short, our pipeline is: Simulated match event → structured event data → Claude accessibility translation → Deepgram voice output → accessible UI / captions / Braille-ready output 🔁 MVP Architecture CupVoice Technical Architecture: User Profile Selection → Simulated Match Event Feed → Structured Event Schema → AI Accessibility Translation Layer → Audio Commentary / Caption Cards / Visual Cues / Braille-Ready Text / ESP32 Hardware Output ⚙️ System Flow 📡 Simulated Match Feed We input football-style live events such as goals, passes, substitutions, and key momentum shifts. 📡 Simulated Match Feed We input football-style live events such as goals, passes, substitutions, and key momentum shifts. 🧩 Structured Event Schema Each event is represented in a predictable format with time, team, player, location, event type, and score impact. 🧩 Structured Event Schema Each event is represented in a predictable format with time, team, player, location, event type, and score impact. 🤖 AI Accessibility Translation Layer The system translates the same event into modality-specific outputs such as audio commentary, caption cards, visual cues, and Braille-ready text. 🤖 AI Accessibility Translation Layer The system translates the same event into modality-specific outputs such as audio commentary, caption cards, visual cues, and Braille-ready text. 📱 Accessible Fan Interface Users interact with the output mode that best fits their accessibility profile. 📱 Accessible Fan Interface Users interact with the output mode that best fits their accessibility profile. 🛠️ Prototype Components Our MVP is designed around the following components: simulated match event feed accessibility profile selection accessible event cards pattern-first visual design Braille-ready text output / simulator multi-modal accessibility translation logic 💻 Tech Stack Frontend: React Backend / AI layer: Node.js + Express with Anthropic Claude API Voice layer: Deepgram TTS with browser Web Speech API fallback Storage / memory: In-memory simulated event feed for the MVP Braille layer: Braille-ready formatted text protocol with ESP32 hardware bridge using Web Serial API 🧗

### Challenges we ran into

One of our biggest challenges was scope. Accessibility is a broad space, and CupVoice could easily have become just a concept deck if we tried to build everything at once. We had to narrow the project into a realistic hackathon MVP while still showing the core idea: one live match event → multiple accessible outputs. We also had to design for users with very different needs. A blind fan, a deaf fan, a deafblind fan, a color-blind fan, and a sensory-sensitive fan do not need the same interface, the same pacing, or the same information density. Another key challenge was balancing speed and clarity. Live sports updates must be fast, but accessibility outputs cannot be vague or overloaded. This was especially important for the Braille-ready stream, where concise, structured information is much more useful than long-form commentary. Finally, because this was a hackathon, we had to simulate the sports feed and focus on the translation layer itself rather than building a full live broadcast ingestion pipeline. 🏆

### Accomplishments we're proud of

We are proud that we turned a broad social impact idea into a concrete, demo-able prototype. Instead of only saying that sports should be more inclusive, we built a system that demonstrates how the same match event can become different accessible experiences for different users. We are especially proud of the Braille-ready pathway. Many accessibility tools stop at captions or audio, but CupVoice also considers users who may not be able to rely on either sight or sound. By creating a structured text protocol, we show how live sports could become more accessible through tactile interfaces as well. We are also proud of designing CupVoice as an example of universal design. It is not a separate simplified version of the game — it is the same match, translated into the format that works best for each fan. 📚

### What we learned

We learned that accessibility is not just one feature — it is a full product design challenge. Different users need different sensory channels, timing, structure, and levels of detail. A good accessibility system should not force everyone into the same experience. It should adapt. We also learned that structured data is incredibly powerful for accessible AI systems. Once match events are represented clearly, they can be translated much more reliably across audio, captions, visual cues, and Braille-ready output. Most importantly, we learned that social impact projects need both empathy and execution. A meaningful idea matters, but the demo needs to clearly show how the user experience changes. 🚀

### What's next

🔧 Improve the hardware prototype Our current hardware setup is still an early prototype. Next, we would improve the stability, portability, and compatibility of the hardware layer so that CupVoice can better support real assistive devices, including refreshable Braille displays and Bluetooth-based accessibility tools. 🌐 Add multilingual commentary Live sports are global, and accessibility should not be limited to one language. We would expand CupVoice to support multilingual audio commentary, captions, and Braille-ready text so that international fans can follow the match in the language that works best for them. 🧩 Support more sensory profiles and personalization Different fans have different accessibility needs. Next, we would add more sensory profiles and personalization settings, allowing users to adjust commentary detail, caption density, visual contrast, pacing, audio intensity, and Braille-ready update formats. Our long-term vision is simple: every fan should be able to experience the same match through the sensory path that works best for them.

## README (from the GitHub repository)

# 🎙️ CupVoice - Accessible Sports Commentary System

![CupVoice Banner](https://via.placeholder.com/1200x300/0066cc/ffffff?text=CupVoice+-+Accessible+Sports+for+Everyone)

> Making live sports accessible for blind, deaf, deafblind, color-blind, and sensory-sensitive fans through AI-powered multi-modal commentary.

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
[![React](https://img.shields.io/badge/React-18+-61DAFB.svg)](https://reactjs.org/)
[![Claude API](https://img.shields.io/badge/Claude-API-blueviolet.svg)](https://anthropic.com/)
[![Deepgram](https://img.shields.io/badge/Deepgram-TTS-orange.svg)](https://deepgram.com/)

---

## 🌍 Inspiration

Live sports are meant to be a shared experience, but the way they are broadcast today is not equally accessible to every fan.

Football matches move fast and rely heavily on sight, sound, color, motion, crowd noise, and dense visual graphics. For **blind, low-vision, deaf, hard-of-hearing, deafblind, color-blind, and sensory-sensitive fans**, the same match can become fragmented, overwhelming, or inaccessible.

We were inspired by a simple question:

**What if the same live match could be translated into the sensory format that works best for each fan?**

That idea led us to build **CupVoice** — a multi-modal accessibility layer for live sports broadcasts.

---

## 🧠 What it does

CupVoice transforms live-style football match events into multiple accessibility-focused outputs.

Instead of showing the same fast-paced broadcast to everyone, our prototype adapts each event to different user needs. A single match event can become:

- **🎧 AI-powered audio commentary** for blind and low-vision fans (powered by Claude API + Deepgram TTS)
- **📝 Dynamic captions and event cards** for deaf and hard-of-hearing fans  
- **⠿ Braille-ready structured text + hardware output** for deafblind users (ESP32 via Web Serial API)
- **👶 Beginner-friendly explanations** for new fans unfamiliar with sports terminology
- **🎨 High-contrast Apple-inspired UI** with accessible color palette and typography

For example, a goal is not just shown as a visual highlight. CupVoice can communicate:

- who scored  
- where the play happened  
- how the ball moved  
- what changed in the score  
- why the moment matters in the match context  

For Braille-ready output, the same event is compressed into a short structured line such as:

```
26:14 | ARG GOAL | Messi | right box | low shot | ARG 1-0 FRA
```

This is automatically sent to a **refreshable Braille display** via ESP32 hardware over USB.

---

## 🏗️ How we built it

We built CupVoice around a **structured live match event pipeline**.

Instead of trying to process a full sports broadcast video feed during the hackathon, we focused on a more reliable MVP: **simulated live match events**. Each event contains structured fields such as:

- time  
- team  
- player  
- event type  
- location  
- score impact  
- match context  

These structured events are then translated into multiple accessibility outputs.

### 🔁 System Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                    CupVoice Architecture                     │
└─────────────────────────────────────────────────────────────┘

┌─────────────────┐
│  User Profile   │  Select: Blind / Deaf / Beginner
│   Selection     │
└────────┬────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│              Simulated Match Event Feed                      │
│  (Soccer, Basketball, American Football live-style events)  │
└────────┬────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│         AI Commentary Generation (Claude API)                │
│  • Beginner mode: Simple, calm, jargon-free explanations    │
│  • Technical mode: Tactical analysis and formations         │
│  • Emotional mode: Energetic, passionate commentary         │
└────────┬────────────────────────────────────────────────────┘
         │
         ├────────────────────┬──────────────────┬─────────────┐
         ▼                    ▼                  ▼             ▼
┌──────────────┐    ┌──────────────┐    ┌─────────────┐  ┌──────────┐
│ Audio Output │    │ Visual Cards │    │   Braille   │  │ Captions │
│ (Deepgram/   │    │ (Real-time   │    │  Hardware   │  │ (Screen) │
│  Browser TTS)│    │  React UI)   │    │  (ESP32 +   │  │          │
│              │    │              │    │  Web Serial)│  │          │
└──────────────┘    └──────────────┘    └─────────────┘  └──────────┘
```

### ⚙️ System Flow

1. **📡 Simulated Match Feed**  
   We input football-style live events such as goals, passes, substitutions, and key momentum shifts every 10-15 seconds.

2. **🧩 Structured Event Schema**  
   Each event is represented in a predictable format with time, team, player, location, event type, and score impact.

3. **🤖 AI Accessibility Translation Layer (Claude API)**  
   The system uses **Anthropic Claude Sonnet 4.5** to translate the same event into modality-specific outputs:
   - Beginner Fan mode: Short, calm, jargon-free explanations
   - Technical mode: Tactical analysis with formations
   - Emotional mode: Energetic, passionate commentary

4. **🔊 Text-to-Speech (Deepgram TTS)**  
   Commentary text is converted to natural speech using **Deepgram's Aura-Asteria voice model** (fallback: browser Web Speech API).

5. **⠿ Braille Hardware Output (ESP32 + Web Serial API)**  
   Structured Braille data is sent to ESP32 microcontroller via USB using Chrome's Web Serial API for physical Braille display control.

6. **📱 Accessible Fan Interface (React + Apple Design)**  
   Users interact with a clean, high-contrast UI inspired by Apple's design system with pill buttons, soft shadows, and SF Pro typography.

---

## 💻 Tech Stack

### **Frontend**
- **React 18** - Component-based UI
- **Axios** - HTTP client for API calls
- **Web Speech API** - Browser-native text-to-speech fallback
- **Web Serial API** - Direct USB communication with ESP32 hardware
- **Apple-inspired CSS** - SF Pro font stack, Action Blue (#0066cc), rounded pill buttons

### **Backend**
- **Node.js + Express** - REST API server
- **Anthropic Claude API** - AI commentary generation (Sonnet 4.5)
- **Deepgram SDK** - Text-to-speech voice synthesis (Aura-Asteria model)
- **WebSocket (ws)** - Real-time hardware event broadcasting
- **dotenv** - Environment variable management

### **Hardware Integration**
- **ESP32 Microcontroller** - Braille display controller
- **Web Serial API** - Browser-to-hardware USB communication at 115200 baud
- **Arduino C++** - ESP32 firmware for serial data parsing

### **AI & Voice**
- **Claude Sonnet 4.5** (`claude-sonnet-4-20250514`) - Natural language commentary generation
- **Deepgram TTS** (`aura-asteria-en` model) - High-quality voice synthesis
- **Ollama (optional)** - Local AI alternative for offline mode

### **Accessibility Features**
- **Braille 6-dot system** - Standard Braille encoding (dots 1-6)
- **ARIA labels** - Full screen reader support
- **Keyboard navigation** - Tab-accessible UI
- **High contrast mode** - `@media (prefers-contrast: high)` support
- **Reduced motion** - `@media (prefers-reduced-motion)` support

### **Deployment**
- **Development:** `npm run dev` (concurrent client + server)
- **Production:** Static build + Node.js backend
- **Environment:** localhost:3000 (client), localhost:3001 (server)

---

## 🚀 Installation & Setup

### Prerequisites
```bash
# Required
Node.js 18+
npm 9+
Chrome or Edge browser (for Web Serial API)

# Optional
ESP32 microcontroller + USB cable
Arduino IDE (for ESP32 firmware upload)
```

### 1. Clone Repository
```bash
git clone https://github.com/Abulala1/cupvoice.git
cd cupvoice
```

### 2. Install Dependencies
```bash
npm run i

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 4 recognized source files, 27 KB.
- Anthropic (technology) — detected in the code
- Express (technology) — detected in the code
- JavaScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- React (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (6 of 6)

```
.gitignore
package.json
README.md
server/index.js
server/services/commentary.js
server/services/tts.js
```

### Dependencies

- package.json: @anthropic-ai/sdk@^0.27.0, @deepgram/sdk@^3.4.0, axios@^1.18.0, concurrently@^8.2.2, cors@^2.8.5, dotenv@^16.4.5, express@^4.18.2, multer@^1.4.5-lts.1, nodemon@^3.1.0, ws@^8.16.0

### Recent commits (newest first)

- Delete ESP32_CupVoice_Braille.ino
- Clean up project: remove documentation and mock files
- Initial commit: CupVoice - Accessible Sports Commentary System

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

### package.json

```
{
  "name": "cpuvoice-ai-commentary",
  "version": "1.0.0",
  "description": "Accessible AI World Cup Commentary System",
  "main": "server/index.js",
  "scripts": {
    "server": "node server/index.js",
    "client": "cd client && npm start",
    "dev": "concurrently \"npm run server\" \"npm run client\"",
    "install-all": "npm install && cd client && npm install"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.27.0",
    "@deepgram/sdk": "^3.4.0",
    "axios": "^1.18.0",
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.18.2",
    "multer": "^1.4.5-lts.1",
    "ws": "^8.16.0"
  },
  "devDependencies": {
    "concurrently": "^8.2.2",
    "nodemon": "^3.1.0"
  }
}

```

### server/index.js

```javascript
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const multer = require('multer');
const path = require('path');
const WebSocket = require('ws');
const http = require('http');

const commentaryService = require('./services/commentary');
const ttsService = require('./services/tts');

console.log(`🤖 AI Commentary: Claude API`);
console.log(`🎙️ Text-to-Speech: Deepgram API`);

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// Middleware
app.use(cors());
app.use(express.json());
app.use('/uploads', express.static('uploads'));

// File upload configuration
const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, 'uploads/'),
  filename: (req, file, cb) => cb(null, Date.now() + path.extname(file.originalname))
});
const upload = multer({
  storage,
  limits: { fileSize: 500 * 1024 * 1024 }, // 500MB
  fileFilter: (req, file, cb) => {
    const allowedTypes = /mp4|webm|mov|avi/;
    const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = allowedTypes.test(file.mimetype);
    if (mimetype && extname) return cb(null, true);
    cb(new Error('Only video files allowed'));
  }
});

// WebSocket connections (for ESP32 hardware)
const hardwareClients = new Set();
wss.on('connection', (ws) => {
  console.log('Hardware client connected');
  hardwareClients.add(ws);
  ws.on('close', () => hardwareClients.delete(ws));
});

function broadcastToHardware(event) {
  const message = JSON.stringify(event);
  hardwareClients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

// Routes
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    claude: !!process.env.CLAUDE_API_KEY,
    deepgram: !!process.env.DEEPGRAM_API_KEY
  });
});

app.post('/api/upload-video', upload.single('video'), (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'No video uploaded' });
  res.json({
    success: true,
    filename: req.file.filename,
    url: `/uploads/${req.file.filename}`
  });
});

app.post('/api/commentary', async (req, res) => {
  try {
    const { event, mode, context } = req.body;

    if (!event) {
      return res.status(400).json({ error: 'Event type required' });
    }

    // Generate commentary using Claude or Mock
    const commentary = await commentaryService.generateCommentary(event, mode, context);

    // Generate audio using Deepgram (optional - fallback to browser TTS)
    let audioBase64 = null;
    try {
      const audioBuffer = await ttsService.textToSpeech(commentary.text);
      audioBase64 = audioBuffer.toString('base64');
    } catch (error) {
      console.log('⚠️  Deepgram TTS failed, browser will handle audio:', error.message);
      // Don't throw - let browser TTS handle it
    }

    // Broadcast to hardware
    broadcastToHardware({
      type: 'event',
      event: event,
      timestamp: Date.now(),
      brailleCode: getBrailleCode(event)
    });

    res.json({
      success: true,
      commentary: commentary.text,
      audio: audioBase64 ? `data:audio/mp3;base64,${audioBase64}` : null,
      mode: commentary.mode
    });
  } catch (error) {
    console.error('Commentary error:', error);
    res.status(500).json({ error: error.message });
  }
});

app.post('/api/auto-commentary', async (req, res) => {
  try {
    const { mode, context, sport } = req.body;

    const commentary = await commentaryService.generateCommentary(
      context || 'action',
      mode || 'beginner',
      { sport: sport || 'soccer' }
    );

    res.json({
      success: true,
      commentary: commentary.text,
      mode: commentary.mode
    });
  } catch (error) {
    console.error('Auto commentary error:', error);
    res.status(500).json({ error: error.message });
  }
});

app.post('/api/hardware/event', (req, res) => {
  const { event, intensity } = req.body;
  broadcastToHardware({ type: 'event', event, intensity, timestamp: Date.now() });
  res.json({ success: true });
});

// Braille encoding for hardware
function getBrailleCode(event) {
  const codes = {
    'goal': [1, 2, 4, 5], // ⠛ (g)
    'foul': [1, 2, 4], // ⠋ (f)
    'corner': [1, 4], // ⠉ (c)
    'freekick': [1, 2, 4, 6], // ⠏ (f)
    'penalty': [1, 2, 3, 4], // ⠏ (p)
    'offside': [1, 3, 5], // ⠕ (o)
    'substitution': [2, 3, 4], // ⠎ (s)
    'yellowcard': [1, 3, 4, 5, 6], // ⠽ (y)
    'redcard': [1, 2, 3, 5], // ⠗ (r)
  };
  return codes[event] || [0];
}

const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
  console.log(`🚀 Server running on port ${PORT}`);
  console.log(`📡 WebSocket ready for hardware clients`);
  console.log(`🎯 Health check: http://localhost:${PORT}/health`);
});

```

### server/services/tts.js

```javascript
const { createClient } = require('@deepgram/sdk');

const deepgram = createClient(process.env.DEEPGRAM_API_KEY);

async function textToSpeech(text) {
  try {
    const response = await deepgram.speak.request(
      { text },
      {
        model: 'aura-asteria-en',
        encoding: 'mp3',
        container: 'mp3'
      }
    );

    // Get the audio stream
    const stream = await response.getStream();
    const chunks = [];

    if (stream) {
      for await (const chunk of stream) {
        chunks.push(chunk);
      }
    }

    return Buffer.concat(chunks);
  } catch (error) {
    console.error('Deepgram TTS error:', error);
    throw new Error('Failed to generate audio: ' + error.message);
  }
}

module.exports = {
  textToSpeech
};

```

### server/services/commentary.js

```javascript
const Anthropic = require('@anthropic-ai/sdk');

const client = new Anthropic({
  apiKey: process.env.CLAUDE_API_KEY,
});

// System prompts for different modes
const PROMPTS = {
  beginner: {
    system: `You are a friendly World Cup commentator for beginners who may not know much about football/soccer.
Use simple language, explain what's happening clearly, and avoid technical jargon.
Keep responses to 2-3 sentences maximum. Be encouraging and enthusiastic but clear.`,
    examples: {
      goal: "What a moment! The player kicked the ball into the net and scored a goal! The crowd is going wild!",
      foul: "Oh no, a player broke the rules by pushing or tripping another player. The referee just blew the whistle to stop play.",
    }
  },
  technical: {
    system: `You are an analytical World Cup commentator focused on tactics, formations, and technical aspects of the game.
Discuss positioning, strategy, player roles, and tactical decisions.
Keep responses to 2-3 sentences maximum. Be insightful and specific.`,
    examples: {
      goal: "Excellent build-up play through the middle third. The striker exploited space between the center-backs with a perfectly timed diagonal run.",
      foul: "Tactical foul in the defensive third to stop a counter-attack. Smart defensive awareness to prevent a dangerous situation.",
    }
  },
  emotional: {
    system: `You are an energetic, passionate World Cup commentator who brings excitement and emotion to every moment.
Use dramatic language, exclamation points, and vivid descriptions. Make the audience FEEL the game!
Keep responses to 2-3 sentences maximum. Be theatrical and electrifying!`,
    examples: {
      goal: "GOOOOOAAAALLLL!!! UNBELIEVABLE! The stadium ERUPTS as the ball thunders into the back of the net! This is what dreams are made of!",
      foul: "OH NO! A crunching challenge! The referee's whistle pierces through the roar of the crowd! That's going to hurt in the morning!",
    }
  }
};

async function generateCommentary(event, mode = 'beginner', context = {}) {
  const selectedMode = PROMPTS[mode] || PROMPTS.beginner;

  // Build context string
  const contextStr = Object.keys(context).length > 0
    ? `\n\nCurrent context: ${JSON.stringify(context, null, 2)}`
    : '';

  const userPrompt = `Event: ${event}${contextStr}

Generate exciting commentary for this World Cup moment. Remember to keep it to 2-3 sentences maximum and match the ${mode} style.`;

  try {
    const response = await client.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 150,
      temperature: 0.8,
      system: selectedMode.system,
      messages: [{
        role: 'user',
        content: userPrompt
      }]
    });

    const text = response.content[0].text.trim();

    return {
      text,
      mode,
      event,
      timestamp: Date.now()
    };
  } catch (error) {
    console.error('Claude API error:', error);
    throw new Error('Failed to generate commentary: ' + error.message);
  }
}

module.exports = {
  generateCommentary
};

```