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:
118
src/game/AdminPanel.jsx
Normal file
118
src/game/AdminPanel.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user