');
root.querySelector('[data-back]')?.addEventListener('click', () => { state.view = { kind: 'catalog' }; render(); });
root.querySelectorAll('[data-lesson]').forEach((b) =>
b.addEventListener('click', () => launchLesson(packId, b.getAttribute('data-lesson'))));
}
// ── navigation into the plugin's own lesson view ─────────────────────---
function launchLesson(packId, lessonId) {
if (!packId || !lessonId) return;
// When the lesson's song auto-exits (or the player is closed), return
// to the lessons catalog to pick the next one — not the song library.
// playSong() consumes this one-shot override even though the external
// tutorials plugin owns the actual playSong call. The legitimate path
// is lessons → plugin-tutorials → player; if the launch is abandoned
// (plugin missing, deep-link fails, user backs out) the override would
// otherwise linger and mis-route the next library song. So clear it on
// the first navigation that leaves the launch flow (any screen other
// than the tutorials waypoint or the player that consumes it).
try {
if (window.feedBack && typeof window.feedBack.setReturnScreen === 'function') {
window.feedBack.setReturnScreen('v3-lessons');
if (sm && typeof sm.on === 'function' && typeof sm.off === 'function') {
const clearStale = (e) => {
const id = e && e.detail && e.detail.id;
if (id === 'plugin-tutorials') return; // expected waypoint — keep waiting
// 'player' means playSong already consumed the override;
// anything else means the launch was abandoned.
if (id !== 'player' && window.feedBack._nextReturnScreen === 'v3-lessons') {
window.feedBack.setReturnScreen(null);
}
sm.off('screen:changed', clearStale);
};
sm.on('screen:changed', clearStale);
}
}
} catch (_e) { /* non-fatal */ }
// navigate() stores nav params AND calls showScreen(); the tutorials
// plugin's init() reads getNavParams() to deep-link to {packId, lessonId}.
if (sm && typeof sm.navigate === 'function') {
sm.navigate('plugin-tutorials', { packId: packId, lessonId: lessonId });
} else if (typeof window.showScreen === 'function') {
// Fallback when navigate() is unavailable: stash the same nav params
// navigate() would set so the tutorials plugin can still deep-link
// instead of landing on the generic browse screen.
if (sm) sm._navParams = { packId: packId, lessonId: lessonId };
window.showScreen('plugin-tutorials');
}
}
async function startPackById(packId) {
// Fetch the manifest so we can resolve the pack's next lesson, then launch.
const pack = await jget(API + '/packs/' + enc(packId));
const next = pack && Array.isArray(pack.lessons) ? nextLesson(pack) : null;
if (next) launchLesson(packId, next.id);
else openPack(packId);
}
function openPack(packId) { state.view = { kind: 'pack', packId: packId }; render(); }
function wireGo(root) {
root.querySelectorAll('[data-go]').forEach((b) =>
b.addEventListener('click', () => window.showScreen && window.showScreen(b.getAttribute('data-go'))));
}
// ── entry ────────────────────────────────────────────────────────────---
async function render() {
const root = document.getElementById('v3-lessons');
if (!root) return;
// Refresh unified progress (level/XP/streak) + per-lesson progress every
// render so XP earned in the plugin shows when the user returns.
const [prog, tut] = await Promise.all([
jget('/api/profile/progress'),
jget(API + '/progress'),
]);
state.progress = tut || { packs: {} };
if (state.view.kind === 'pack') await renderPack(root, prog, state.view.packId);
else await renderCatalog(root, prog);
}
window.v3Lessons = { render: render };
if (sm && typeof sm.on === 'function') {
sm.on('screen:changed', (e) => { if (e && e.detail && e.detail.id === 'v3-lessons') render(); });
sm.on('v3:profile-updated', () => { if (document.getElementById('v3-lessons')?.classList.contains('active')) render(); });
// Refresh the header rank/dB strip when progression state changes while
// the screen is already visible (e.g. after a minigame run on this screen).
sm.on('progression:updated', () => { if (document.getElementById('v3-lessons')?.classList.contains('active')) render(); });
}
})();