feat: admin mode, worlds 4-6, and stereo output fix

- Admin panel: add/remove stars, unlock worlds, reset progress (🛠 button)
- World 4 "Modulación" (8 levels): vibrato, sirena, wah-wah, auto-pan, FM, wobble bass
- World 5 "Efectos" (8 levels): delay, slapback, reverb, distortion, dub echo, shoegaze, ambient
- World 6 "Diseño Sonoro" (8 levels): kick, hi-hat, snare, pad, reese bass, laser, trance arp, final boss
- Star unlock progression: W4=36★, W5=48★, W6=60★ (total 48 levels, 144 stars)
- Fix stereo output: left/right channels now route through Tone.Merge for true stereo separation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 02:38:17 +01:00
parent 41d993183f
commit c4a2cb3cef
8 changed files with 1658 additions and 12 deletions

118
src/game/AdminPanel.jsx Normal file
View File

@@ -0,0 +1,118 @@
/**
* AdminPanel.jsx — Debug/admin panel for SynthQuest
* Allows adding/removing stars and unlocking levels for testing
*/
import React, { useState } from 'react';
import { loadProgress, saveProgress, resetProgress } from './gameState.js';
export default function AdminPanel({ worlds, onClose }) {
const [, refresh] = useState(0);
const p = loadProgress();
const totalStars = Object.values(p.completedLevels).reduce((s, l) => s + (l.stars || 0), 0);
const setStars = (levelId, stars) => {
if (stars <= 0) {
delete p.completedLevels[levelId];
} else {
p.completedLevels[levelId] = { stars: Math.min(3, stars), completedAt: Date.now() };
}
saveProgress();
refresh(n => n + 1);
};
const unlockWorld = (world) => {
// Give 1 star to each level in all previous worlds up to the requirement
let needed = world.unlockStars || 0;
for (const w of worlds) {
if (w.id === world.id) break;
for (const level of w.levels) {
if (needed <= 0) break;
const existing = p.completedLevels[level.id]?.stars || 0;
if (existing < 1) {
p.completedLevels[level.id] = { stars: 1, completedAt: Date.now() };
needed -= 1;
}
}
}
saveProgress();
refresh(n => n + 1);
};
const giveAllStars = () => {
for (const w of worlds) {
for (const level of w.levels) {
p.completedLevels[level.id] = { stars: 3, completedAt: Date.now() };
}
}
saveProgress();
refresh(n => n + 1);
};
const handleReset = () => {
resetProgress();
refresh(n => n + 1);
};
return (
<div className="admin-overlay" onClick={onClose}>
<div className="admin-panel" onClick={e => e.stopPropagation()}>
<div className="admin-header">
<h2>🛠 Admin Mode</h2>
<span className="admin-total">Total: {totalStars}</span>
<button className="admin-close" onClick={onClose}></button>
</div>
<div className="admin-actions">
<button className="admin-action-btn gold" onClick={giveAllStars}> Todo</button>
<button className="admin-action-btn danger" onClick={handleReset}>Reset Progreso</button>
</div>
<div className="admin-worlds">
{worlds.map((world, wi) => {
const worldStars = world.levels.reduce((s, l) => {
return s + (p.completedLevels[l.id]?.stars || 0);
}, 0);
const isUnlocked = !world.unlockStars || totalStars >= world.unlockStars;
return (
<div key={world.id} className="admin-world">
<div className="admin-world-header">
<span className="admin-world-icon" style={{ color: world.color }}>{world.icon}</span>
<span className="admin-world-name">M{wi + 1}: {world.name}</span>
<span className="admin-world-stars"> {worldStars}/{world.levels.length * 3}</span>
{!isUnlocked && (
<button className="admin-unlock-btn" onClick={() => unlockWorld(world)}>
🔓 Desbloquear
</button>
)}
</div>
<div className="admin-levels">
{world.levels.map((level, li) => {
const stars = p.completedLevels[level.id]?.stars || 0;
return (
<div key={level.id} className="admin-level">
<span className="admin-level-num">{wi + 1}.{li + 1}</span>
<span className="admin-level-name">{level.title}</span>
<div className="admin-star-btns">
{[0, 1, 2, 3].map(s => (
<button
key={s}
className={`admin-star-btn ${stars >= s && s > 0 ? 'active' : ''} ${s === 0 ? 'zero' : ''}`}
onClick={() => setStars(level.id, s)}
>
{s === 0 ? '✕' : '★'.repeat(s)}
</button>
))}
</div>
</div>
);
})}
</div>
</div>
);
})}
</div>
</div>
</div>
);
}