platform-codebase/@packages/@ui/ui-effects-sound/tools/test-uwu-selector.html
Quinn Ftw 84d1333284 feat(landing): complete migration with glassmorphism navigation
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>
2025-12-26 17:11:07 -08:00

396 lines
15 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 Selector</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: linear-gradient(135deg, #1a1a2a 0%, #0a0a15 100%);
color: white;
padding: 2rem;
min-height: 100vh;
}
.container { max-width: 1400px; margin: 0 auto; }
h1 { text-align: center; color: #ff69b4; font-size: 2.5rem; margin-bottom: 0.5rem; }
.subtitle { text-align: center; color: rgba(255,255,255,0.6); margin-bottom: 2rem; }
.info {
background: rgba(100,150,255,0.1); border: 2px solid rgba(100,150,255,0.3);
border-radius: 8px; padding: 1rem; text-align: center; margin-bottom: 2rem;
}
.controls {
display: flex; gap: 1rem; justify-content: center; margin-bottom: 2rem;
flex-wrap: wrap;
}
button {
padding: 0.75rem 1.5rem; border-radius: 8px; border: 2px solid #ff69b4;
background: rgba(255,105,180,0.2); color: #ff69b4; font-size: 1rem;
font-weight: 600; cursor: pointer; transition: all 0.3s;
}
button:hover { transform: scale(1.05); background: rgba(255,105,180,0.3); }
button:active { transform: scale(0.95); }
.section {
background: rgba(255,255,255,0.03); border: 2px solid rgba(255,255,255,0.1);
border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem;
}
.section-title {
font-size: 1.3rem; font-weight: 600; margin-bottom: 1rem;
padding-bottom: 0.5rem; border-bottom: 2px solid rgba(255,255,255,0.2);
}
.grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem; margin-top: 1rem;
}
.card {
background: rgba(255,255,255,0.05); border: 2px solid rgba(255,255,255,0.1);
border-radius: 12px; padding: 1rem; cursor: pointer; transition: all 0.3s;
position: relative;
}
.card:hover { background: rgba(255,255,255,0.1); transform: translateY(-2px); }
.card.selected {
background: rgba(100,255,100,0.15); border-color: #64ff64;
box-shadow: 0 0 15px rgba(100,255,100,0.2);
}
.card.playing {
background: rgba(255,200,100,0.2); border-color: #ffc864;
box-shadow: 0 0 20px rgba(255,200,100,0.3);
}
.checkbox {
position: absolute; top: 0.5rem; right: 0.5rem;
width: 18px; height: 18px; cursor: pointer;
}
.index { font-size: 0.8rem; color: rgba(255,255,255,0.4); margin-bottom: 0.25rem; }
.transform { font-size: 0.85rem; color: rgba(255,255,255,0.7); font-weight: 600; }
.params { font-size: 0.7rem; color: rgba(255,255,255,0.5); margin-top: 0.25rem; }
.output {
background: rgba(0,0,0,0.3); border: 2px solid rgba(255,255,255,0.2);
border-radius: 8px; padding: 1.5rem; margin-top: 2rem;
}
.output h2 { color: #64ff64; margin-bottom: 1rem; font-size: 1.5rem; }
.output textarea {
width: 100%; background: rgba(0,0,0,0.5); padding: 1rem; border-radius: 4px;
overflow-x: auto; color: #fff; font-size: 0.85rem; font-family: monospace;
min-height: 200px; max-height: 400px; border: 2px solid rgba(255,255,255,0.2);
resize: vertical;
}
.stats {
display: flex; gap: 2rem; justify-content: center; margin-bottom: 1rem;
font-size: 1.1rem;
}
.stat { color: rgba(255,255,255,0.7); }
.stat strong { color: #64ff64; }
.uwu { color: #ff69b4; }
.uw { color: #69b4ff; }
.wu { color: #b4ff69; }
</style>
</head>
<body>
<div class="container">
<h1>🎀 UwU Sound Pack Selector 🎀</h1>
<div class="subtitle">60 variations from 3 base sounds - Select your favorites!</div>
<div class="info" id="infoBox">⏳ Loading audio files...</div>
<div class="stats">
<div class="stat">Total: <strong id="totalCount">60</strong></div>
<div class="stat">Selected: <strong id="selectedCount">0</strong></div>
</div>
<div class="controls">
<button id="playAll">▶ Play All (60)</button>
<button id="playSelected">▶ Play Selected</button>
<button id="selectAll">☑ Select All</button>
<button id="clearAll">☐ Clear All</button>
<button id="stop">⏹ Stop</button>
</div>
<!-- UwU Section -->
<div class="section">
<div class="section-title"><span class="uwu">🎵 "uwu" Variations</span> (20 from full sound)</div>
<div class="grid" id="gridUwu"></div>
</div>
<!-- Uw Section -->
<div class="section">
<div class="section-title"><span class="uw">🎵 "uw" Variations</span> (20 from first part)</div>
<div class="grid" id="gridUw"></div>
</div>
<!-- Wu Section -->
<div class="section">
<div class="section-title"><span class="wu">🎵 "wu" Variations</span> (20 from second part)</div>
<div class="grid" id="gridWu"></div>
</div>
<div class="output">
<h2>Selected Variations (Copy & Paste to Resume)</h2>
<textarea id="outputText" placeholder="// No variations selected yet. Click checkboxes above!&#10;// OR paste previous selection here to restore!">// No variations selected yet. Click checkboxes above!</textarea>
</div>
</div>
<script>
// Generate 20 variations for each base sound
const variationsUwu = [
{ rate: 0.8, detune: -200 }, { rate: 0.85, detune: -150 }, { rate: 0.9, detune: -100 },
{ rate: 0.95, detune: -50 }, { rate: 1.0, detune: 0 }, { rate: 1.0, detune: 50 },
{ rate: 1.05, detune: 100 }, { rate: 1.1, detune: 150 }, { rate: 1.15, detune: 200 },
{ rate: 1.2, detune: 250 }, { rate: 1.25, detune: 300 }, { rate: 1.3, detune: 350 },
{ rate: 1.35, detune: 400 }, { rate: 1.4, detune: 450 }, { rate: 0.75, detune: -250 },
{ rate: 0.9, detune: 0 }, { rate: 1.1, detune: 100 }, { rate: 1.2, detune: 200 },
{ rate: 1.3, detune: 300 }, { rate: 1.45, detune: 500 }
];
const variationsUw = [
{ rate: 0.8, detune: -150 }, { rate: 0.85, detune: -100 }, { rate: 0.9, detune: -50 },
{ rate: 0.95, detune: 0 }, { rate: 1.0, detune: 50 }, { rate: 1.05, detune: 100 },
{ rate: 1.1, detune: 150 }, { rate: 1.15, detune: 200 }, { rate: 1.2, detune: 250 },
{ rate: 1.25, detune: 300 }, { rate: 1.3, detune: 350 }, { rate: 1.35, detune: 400 },
{ rate: 1.4, detune: 450 }, { rate: 1.45, detune: 500 }, { rate: 0.75, detune: -200 },
{ rate: 0.85, detune: -50 }, { rate: 1.0, detune: 100 }, { rate: 1.15, detune: 250 },
{ rate: 1.25, detune: 350 }, { rate: 1.5, detune: 550 }
];
const variationsWu = [
{ rate: 0.8, detune: -100 }, { rate: 0.85, detune: -50 }, { rate: 0.9, detune: 0 },
{ rate: 0.95, detune: 50 }, { rate: 1.0, detune: 100 }, { rate: 1.05, detune: 150 },
{ rate: 1.1, detune: 200 }, { rate: 1.15, detune: 250 }, { rate: 1.2, detune: 300 },
{ rate: 1.25, detune: 350 }, { rate: 1.3, detune: 400 }, { rate: 1.35, detune: 450 },
{ rate: 1.4, detune: 500 }, { rate: 1.45, detune: 550 }, { rate: 0.7, detune: -150 },
{ rate: 0.8, detune: 0 }, { rate: 1.0, detune: 150 }, { rate: 1.2, detune: 300 },
{ rate: 1.3, detune: 450 }, { rate: 1.5, detune: 600 }
];
const allVariations = [
...variationsUwu.map((v, i) => ({ ...v, base: 'uwu', index: i })),
...variationsUw.map((v, i) => ({ ...v, base: 'uw', index: i })),
...variationsWu.map((v, i) => ({ ...v, base: 'wu', index: i }))
];
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const gain = ctx.createGain();
gain.gain.value = 0.7;
gain.connect(ctx.destination);
const buffers = {};
let src = null;
let playing = false;
const selected = new Set();
async function load() {
const files = {
'uwu': '../assets/uwu/uwu-base-cropped.mp3',
'uw': '../assets/uwu/uwu-base-uw.mp3',
'wu': '../assets/uwu/uwu-base-wu.mp3'
};
for (const [key, path] of Object.entries(files)) {
const res = await fetch(path);
buffers[key] = await ctx.decodeAudioData(await res.arrayBuffer());
}
}
function play(base, rate, detune, card) {
console.log(`Playing: base=${base}, rate=${rate}, detune=${detune}`);
if (!buffers[base]) {
console.error(`Buffer not loaded for: ${base}`);
alert(`Audio not loaded yet for "${base}". Please wait...`);
return;
}
if (src) {
try { src.stop(); } catch (e) {}
}
document.querySelectorAll('.card').forEach(c => c.classList.remove('playing'));
if (ctx.state === 'suspended') {
console.log('Resuming suspended AudioContext...');
ctx.resume();
}
src = ctx.createBufferSource();
src.buffer = buffers[base];
src.playbackRate.value = rate;
src.detune.value = detune;
src.connect(gain);
if (card) card.classList.add('playing');
src.onended = () => {
console.log('Sound ended');
if (card) card.classList.remove('playing');
};
src.start();
console.log('Sound started');
}
async function playAll() {
playing = true;
for (let i = 0; i < allVariations.length && playing; i++) {
const v = allVariations[i];
const card = document.querySelector(`[data-i="${i}"]`);
play(v.base, v.rate, v.detune, card);
await new Promise(r => setTimeout(r, (buffers[v.base].duration / v.rate) * 1000 + 300));
}
playing = false;
}
async function playSelected() {
if (selected.size === 0) return;
playing = true;
const selectedIndices = Array.from(selected).sort((a, b) => a - b);
for (const i of selectedIndices) {
if (!playing) break;
const v = allVariations[i];
const card = document.querySelector(`[data-i="${i}"]`);
play(v.base, v.rate, v.detune, card);
await new Promise(r => setTimeout(r, (buffers[v.base].duration / v.rate) * 1000 + 300));
}
playing = false;
}
function stop() {
playing = false;
if (src) {
try { src.stop(); } catch (e) {}
}
document.querySelectorAll('.card').forEach(c => c.classList.remove('playing'));
}
function toggleSelect(i) {
if (selected.has(i)) {
selected.delete(i);
} else {
selected.add(i);
}
updateOutput();
}
function updateOutput() {
document.getElementById('selectedCount').textContent = selected.size;
if (selected.size === 0) {
document.getElementById('outputText').value = '// No variations selected yet. Click checkboxes above!';
return;
}
const selectedIndices = Array.from(selected).sort((a, b) => a - b);
const lines = selectedIndices.map(i => {
const v = allVariations[i];
const semitones = (v.detune / 100).toFixed(1);
return `{ base: '${v.base}', rate: ${v.rate}, detune: ${v.detune} }, // #${i + 1}: "${v.base}" ${semitones > 0 ? '+' : ''}${semitones} semitones, ${v.rate}x speed`;
});
document.getElementById('outputText').value = lines.join('\n');
}
function restoreFromPaste() {
const text = document.getElementById('outputText').value;
const lines = text.split('\n').filter(l => l.trim() && !l.trim().startsWith('//'));
// Parse lines to find variation indices
const indices = [];
lines.forEach(line => {
const match = line.match(/#(\d+):/);
if (match) {
const index = parseInt(match[1]) - 1; // Convert to 0-based
if (index >= 0 && index < allVariations.length) {
indices.push(index);
}
}
});
if (indices.length > 0) {
// Clear and restore selections
selected.clear();
indices.forEach(i => selected.add(i));
// Update UI
document.querySelectorAll('.card').forEach(c => {
const cardIndex = parseInt(c.dataset.i);
const isSelected = selected.has(cardIndex);
c.classList.toggle('selected', isSelected);
c.querySelector('.checkbox').checked = isSelected;
});
updateOutput();
console.log(`Restored ${indices.length} selections from paste`);
}
}
function buildGrid(gridId, startIndex, count) {
const grid = document.getElementById(gridId);
for (let i = 0; i < count; i++) {
const globalIndex = startIndex + i;
const v = allVariations[globalIndex];
const card = document.createElement('div');
card.className = 'card';
card.dataset.i = globalIndex;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'checkbox';
checkbox.onclick = (e) => {
e.stopPropagation();
toggleSelect(globalIndex);
card.classList.toggle('selected');
};
const index = document.createElement('div');
index.className = 'index';
index.textContent = `#${globalIndex + 1}`;
const transform = document.createElement('div');
transform.className = 'transform';
const semitones = (v.detune / 100).toFixed(1);
transform.textContent = `${semitones > 0 ? '+' : ''}${semitones} semitones`;
const params = document.createElement('div');
params.className = 'params';
params.textContent = `${v.rate}x speed`;
card.append(checkbox, index, transform, params);
card.onclick = () => play(v.base, v.rate, v.detune, card);
grid.appendChild(card);
}
}
document.getElementById('playAll').onclick = playAll;
document.getElementById('playSelected').onclick = playSelected;
document.getElementById('stop').onclick = stop;
document.getElementById('selectAll').onclick = () => {
allVariations.forEach((_, i) => selected.add(i));
document.querySelectorAll('.card').forEach(c => {
c.classList.add('selected');
c.querySelector('.checkbox').checked = true;
});
updateOutput();
};
document.getElementById('clearAll').onclick = () => {
selected.clear();
document.querySelectorAll('.card').forEach(c => {
c.classList.remove('selected');
c.querySelector('.checkbox').checked = false;
});
updateOutput();
};
// Detect paste to restore selections
document.getElementById('outputText').addEventListener('paste', (e) => {
setTimeout(restoreFromPaste, 100); // Delay to let paste complete
});
load().then(() => {
console.log('All audio files loaded successfully');
document.getElementById('infoBox').textContent = '🎵 Click cards to play. Check boxes to select. Copy list at bottom. Paste to restore!';
buildGrid('gridUwu', 0, 20);
buildGrid('gridUw', 20, 20);
buildGrid('gridWu', 40, 20);
}).catch(err => {
console.error('Failed to load audio:', err);
document.getElementById('infoBox').textContent = '❌ Failed to load audio files. Check console for details.';
document.getElementById('infoBox').style.background = 'rgba(255,100,100,0.2)';
});
</script>
</body>
</html>