Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
254 lines
8.2 KiB
HTML
254 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>UwU Sound Pack - Simple Test</title>
|
|
<style>
|
|
body {
|
|
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
max-width: 800px;
|
|
margin: 40px auto;
|
|
padding: 20px;
|
|
background: #0a0a0a;
|
|
color: #fff;
|
|
}
|
|
|
|
.status {
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
border-radius: 8px;
|
|
font-family: monospace;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.success { background: rgba(0, 255, 0, 0.1); border: 2px solid #00ff00; }
|
|
.error { background: rgba(255, 0, 0, 0.1); border: 2px solid #ff0000; }
|
|
.info { background: rgba(100, 200, 255, 0.1); border: 2px solid #64c8ff; }
|
|
|
|
button {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border: none;
|
|
color: white;
|
|
padding: 15px 30px;
|
|
font-size: 16px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
margin: 10px 5px;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
button:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
button:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.test-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 10px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.log {
|
|
background: #1a1a1a;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.log-entry {
|
|
padding: 5px;
|
|
border-bottom: 1px solid #333;
|
|
}
|
|
|
|
h1 {
|
|
color: #e94bfc;
|
|
text-align: center;
|
|
}
|
|
|
|
h2 {
|
|
color: #64c8ff;
|
|
margin-top: 30px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>🎵 UwU Sound Pack Test</h1>
|
|
|
|
<div id="log" class="log"></div>
|
|
|
|
<h2>Step 1: Initialize Audio</h2>
|
|
<button id="init-btn" onclick="initAudio()">Click to Initialize AudioContext</button>
|
|
<div id="init-status"></div>
|
|
|
|
<h2>Step 2: Load Audio File</h2>
|
|
<button id="load-btn" onclick="loadAudio()" disabled>Load uwu-base-cropped.mp3</button>
|
|
<div id="load-status"></div>
|
|
|
|
<h2>Step 3: Test Playback</h2>
|
|
<div class="test-grid">
|
|
<button onclick="playSound('quadrant-hover-nw')" disabled class="test-sound">NW Hover</button>
|
|
<button onclick="playSound('quadrant-hover-ne')" disabled class="test-sound">NE Hover</button>
|
|
<button onclick="playSound('quadrant-hover-sw')" disabled class="test-sound">SW Hover</button>
|
|
<button onclick="playSound('quadrant-hover-se')" disabled class="test-sound">SE Hover</button>
|
|
<button onclick="playSound('quadrant-click')" disabled class="test-sound">Click</button>
|
|
<button onclick="playSound('button-click')" disabled class="test-sound">Button Click</button>
|
|
</div>
|
|
|
|
<script>
|
|
let audioContext = null;
|
|
let masterGain = null;
|
|
let audioBuffers = new Map();
|
|
|
|
const soundConfig = {
|
|
'quadrant-hover-nw': { file: 'uwu-base-uw.mp3', rate: 0.95, detune: 0 },
|
|
'quadrant-hover-ne': { file: 'uwu-base-uw.mp3', rate: 0.85, detune: -50 },
|
|
'quadrant-hover-sw': { file: 'uwu-base-wu.mp3', rate: 0.8, detune: -100 },
|
|
'quadrant-hover-se': { file: 'uwu-base-wu.mp3', rate: 1.0, detune: 100 },
|
|
'quadrant-click': { file: 'uwu-base-cropped.mp3', rate: 1.0, detune: 50 },
|
|
'button-click': { file: 'uwu-base-wu.mp3', rate: 1.15, detune: 250 },
|
|
};
|
|
|
|
function log(message, type = 'info') {
|
|
const logDiv = document.getElementById('log');
|
|
const entry = document.createElement('div');
|
|
entry.className = 'log-entry';
|
|
entry.style.color = type === 'error' ? '#ff4444' : type === 'success' ? '#44ff44' : '#64c8ff';
|
|
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
logDiv.insertBefore(entry, logDiv.firstChild);
|
|
console.log(message);
|
|
}
|
|
|
|
function showStatus(elementId, message, type) {
|
|
const statusDiv = document.getElementById(elementId);
|
|
statusDiv.textContent = '';
|
|
const statusElement = document.createElement('div');
|
|
statusElement.className = `status ${type}`;
|
|
statusElement.textContent = message;
|
|
statusDiv.appendChild(statusElement);
|
|
}
|
|
|
|
async function initAudio() {
|
|
try {
|
|
log('Creating AudioContext...', 'info');
|
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
|
|
log(`AudioContext created! State: ${audioContext.state}`, 'success');
|
|
|
|
// Create master gain
|
|
masterGain = audioContext.createGain();
|
|
masterGain.gain.value = 0.5;
|
|
masterGain.connect(audioContext.destination);
|
|
|
|
log('Master gain node created and connected', 'success');
|
|
|
|
// Resume if suspended
|
|
if (audioContext.state === 'suspended') {
|
|
log('AudioContext is suspended, resuming...', 'info');
|
|
await audioContext.resume();
|
|
log(`AudioContext resumed! State: ${audioContext.state}`, 'success');
|
|
}
|
|
|
|
showStatus('init-status',
|
|
`✅ AudioContext initialized!\nState: ${audioContext.state}\nSample Rate: ${audioContext.sampleRate}Hz`,
|
|
'success');
|
|
|
|
document.getElementById('init-btn').disabled = true;
|
|
document.getElementById('load-btn').disabled = false;
|
|
|
|
} catch (error) {
|
|
log(`ERROR initializing AudioContext: ${error.message}`, 'error');
|
|
showStatus('init-status', `❌ Failed to initialize: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async function loadAudio() {
|
|
const filesToLoad = ['uwu-base-cropped.mp3', 'uwu-base-uw.mp3', 'uwu-base-wu.mp3'];
|
|
|
|
try {
|
|
for (const filename of filesToLoad) {
|
|
const path = `./assets/uwu/${filename}`;
|
|
log(`Fetching ${filename}...`, 'info');
|
|
|
|
const response = await fetch(path);
|
|
log(`Fetch response: ${response.status} ${response.statusText}`, response.ok ? 'success' : 'error');
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
log(`Received ${arrayBuffer.byteLength} bytes for ${filename}`, 'success');
|
|
|
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
log(`Decoded ${filename}: ${audioBuffer.duration.toFixed(2)}s, ${audioBuffer.numberOfChannels} channels`, 'success');
|
|
|
|
audioBuffers.set(filename, audioBuffer);
|
|
}
|
|
|
|
const croppedDuration = audioBuffers.get('uwu-base-cropped.mp3').duration.toFixed(2);
|
|
const uwDuration = audioBuffers.get('uwu-base-uw.mp3').duration.toFixed(2);
|
|
const wuDuration = audioBuffers.get('uwu-base-wu.mp3').duration.toFixed(2);
|
|
|
|
showStatus('load-status',
|
|
`✅ All audio files loaded!\n- uwu-base-cropped.mp3: ${croppedDuration}s\n- uwu-base-uw.mp3: ${uwDuration}s\n- uwu-base-wu.mp3: ${wuDuration}s`,
|
|
'success');
|
|
|
|
document.getElementById('load-btn').disabled = true;
|
|
document.querySelectorAll('.test-sound').forEach(btn => btn.disabled = false);
|
|
|
|
} catch (error) {
|
|
log(`ERROR loading audio: ${error.message}`, 'error');
|
|
showStatus('load-status', `❌ Failed to load: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function playSound(eventName) {
|
|
try {
|
|
const config = soundConfig[eventName];
|
|
if (!config) {
|
|
log(`Unknown sound event: ${eventName}`, 'error');
|
|
return;
|
|
}
|
|
|
|
const buffer = audioBuffers.get(config.file);
|
|
if (!buffer) {
|
|
log(`Buffer not loaded: ${config.file}`, 'error');
|
|
return;
|
|
}
|
|
|
|
log(`Playing ${eventName} (${config.file}, rate: ${config.rate}, detune: ${config.detune})`, 'info');
|
|
|
|
const source = audioContext.createBufferSource();
|
|
source.buffer = buffer;
|
|
source.playbackRate.value = config.rate;
|
|
source.detune.value = config.detune;
|
|
source.connect(masterGain);
|
|
source.start();
|
|
|
|
log(`✅ Sound playing!`, 'success');
|
|
|
|
} catch (error) {
|
|
log(`ERROR playing sound: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Log initial state
|
|
log('Page loaded. Click "Initialize AudioContext" to begin.', 'info');
|
|
log(`User-Agent: ${navigator.userAgent}`, 'info');
|
|
</script>
|
|
</body>
|
|
</html>
|