import type { Progress, LevelStatus } from "../types"; import { levels } from "../game/levels"; import { getProgress, getForestElements } from "../storage/db"; import { playForestElementSound } from "../audio/sounds"; let activeObjectUrls: string[] = []; export async function renderForestScene( db: IDBDatabase, newElementId?: number, ): Promise { const grid = document.getElementById("forest-grid"); if (!grid) return; // Revoke old object URLs to prevent memory leaks for (const url of activeObjectUrls) { URL.revokeObjectURL(url); } activeObjectUrls = []; const elements = await getForestElements(db); grid.innerHTML = ""; // Render up to 12 elements (4x3 grid) const maxSlots = 12; const toRender = elements.slice(0, maxSlots); for (const element of toRender) { const slot = document.createElement("div"); slot.className = "forest__element"; const img = document.createElement("img"); const objectUrl = URL.createObjectURL(element.imageBlob); activeObjectUrls.push(objectUrl); img.src = objectUrl; img.alt = element.description; // Random offset +-10-20px for natural placement const offsetX = (Math.random() > 0.5 ? 1 : -1) * (10 + Math.random() * 10); const offsetY = (Math.random() > 0.5 ? 1 : -1) * (10 + Math.random() * 10); img.style.transform = `translate(${Math.round(offsetX)}px, ${Math.round(offsetY)}px)`; // Animate newly added element if (element.id === newElementId) { slot.classList.add("forest__element--new"); playForestElementSound(); } slot.appendChild(img); grid.appendChild(slot); } } export function getLevelStatus( levelNum: number, progress: Progress, ): LevelStatus { if (progress.completedLevels.includes(levelNum)) return "completed"; if (levelNum === progress.currentLevel) return "current"; return "locked"; } export function renderLevelMap( container: HTMLElement, progress: Progress, onLevelClick: (level: number) => void, ): void { container.innerHTML = ""; levels.forEach((level) => { const status = getLevelStatus(level.level, progress); const card = document.createElement("button"); card.className = `level-card level-card--${status}`; card.dataset.level = String(level.level); const statusIcon = status === "completed" ? "\u2713" : status === "current" ? "\u2192" : "\uD83D\uDD12"; const keysDisplay = level.newKeys .filter((k) => k !== " ") .map((k) => k.toUpperCase()) .join(" "); card.innerHTML = ` Stufe ${level.level} ${keysDisplay}${level.newKeys.includes(" ") ? " + Leertaste" : ""} ${statusIcon} `; // Only current and completed levels are clickable if (status !== "locked") { card.addEventListener("click", () => onLevelClick(level.level)); } else { card.disabled = true; } container.appendChild(card); }); } const MAX_LEVEL = 6; export async function initForestScreen( db: IDBDatabase, onStartLesson: (level: number) => void, ): Promise { const mapContainer = document.getElementById("level-map")!; const continueBtn = document.getElementById( "continue-btn", ) as HTMLButtonElement; const progress = await getProgress(db); if (!progress) return; renderLevelMap(mapContainer, progress, onStartLesson); await renderForestScene(db); const allComplete = levels.every((l) => progress.completedLevels.includes(l.level), ); if (allComplete) { continueBtn.textContent = "Alle Stufen geschafft!"; continueBtn.disabled = true; continueBtn.classList.add("forest__continue-btn--done"); } else { continueBtn.textContent = "Weiter ueben"; continueBtn.disabled = false; continueBtn.classList.remove("forest__continue-btn--done"); continueBtn.onclick = () => onStartLesson(Math.min(progress.currentLevel, MAX_LEVEL)); } }