fix: LFO→cutoff modulation, visual knob feedback, persistent hints

- Fix LFO→Filter cutoff: add scaling Gain nodes so LFO (-1..1) maps to
  meaningful Hz modulation (±cutoff value). Same fix for LFO→Osc freq.
  Mod scale updates dynamically when user changes the base param value.
- Visual modulation indicator: knobs receiving LFO/modulation show a
  pulsing dashed ring animation (spin + pulse) around the knob arc
- Persist hint usage per level: using a hint permanently caps that level
  at 2 stars — survives reload/restart. No more cheating by restarting!
- Hint state stored in separate localStorage key (synthquest-hints)
- Admin reset also clears hint history

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 02:44:28 +01:00
parent c4a2cb3cef
commit f0100eb64f
7 changed files with 120 additions and 15 deletions

View File

@@ -61,8 +61,10 @@ export function isLevelUnlocked(levelId, worldLevels) {
export function resetProgress() {
_progress = { ...defaultProgress };
_patches = {};
_hints = {};
saveProgress();
savePatches();
saveHints();
}
// ==================== Level patch persistence ====================
@@ -105,3 +107,43 @@ export function clearLevelPatch(levelId) {
delete patches[levelId];
savePatches();
}
// ==================== Hint tracking (persisted, no cheating!) ====================
const HINTS_KEY = 'synthquest-hints';
let _hints = null; // { levelId: true }
function loadHints() {
if (_hints) return _hints;
try {
const raw = localStorage.getItem(HINTS_KEY);
_hints = raw ? JSON.parse(raw) : {};
} catch {
_hints = {};
}
return _hints;
}
function saveHints() {
if (!_hints) return;
try {
localStorage.setItem(HINTS_KEY, JSON.stringify(_hints));
} catch {}
}
export function markHintUsed(levelId) {
const hints = loadHints();
hints[levelId] = true;
saveHints();
}
export function wasHintUsed(levelId) {
const hints = loadHints();
return !!hints[levelId];
}
export function clearHintForLevel(levelId) {
const hints = loadHints();
delete hints[levelId];
saveHints();
}