diff --git a/src/App.svelte b/src/App.svelte index df5b9e7..402d02c 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,6 +1,9 @@
@@ -30,13 +48,13 @@ {:else if currentScene === 'StoryIntro'} {:else if currentScene === 'LevelIntro'} - + {:else if currentScene === 'LevelSelect'} {:else if currentScene === 'Game'} - + {:else if currentScene === 'GameOver'} - + {:else if currentScene === 'Settings'} {:else if currentScene === 'Guide'} @@ -44,6 +62,17 @@ {/if}
{/key} + + {#if showMigrationModal} + + + {/if} diff --git a/src/lib/progress.js b/src/lib/progress.js index 68ce494..3351abd 100644 --- a/src/lib/progress.js +++ b/src/lib/progress.js @@ -1,6 +1,7 @@ // Level progress persistence via localStorage const STORAGE_KEY = 'nntv-progress'; +const MIGRATION_KEY = 'nntv-migration-v2'; const DEFAULT_PROGRESS = { maxLevel: 1, @@ -9,22 +10,63 @@ const DEFAULT_PROGRESS = { levelBestMoves: {}, }; -export function getProgress() { +/** + * Returns { progress, needsMigrationModal }. + * If saved data has legacy `lives` field (v1 shape), discards it and sets + * needsMigrationModal = true so App.svelte can show the one-time modal. + */ +export function loadProgress() { try { const data = localStorage.getItem(STORAGE_KEY); if (data) { const parsed = JSON.parse(data); + // Detect v1 save shape (has `lives` field) + if (parsed.lives !== undefined) { + // Wipe legacy data, save clean v2 shape + const fresh = { ...DEFAULT_PROGRESS }; + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(fresh)); + } catch (_) { + // Storage full — proceed with in-memory defaults + } + return { progress: fresh, needsMigrationModal: true }; + } return { - maxLevel: parsed.maxLevel || 1, - completedLevels: parsed.completedLevels || [], - levelStars: parsed.levelStars || {}, - levelBestMoves: parsed.levelBestMoves || {}, + progress: { + maxLevel: parsed.maxLevel || 1, + completedLevels: parsed.completedLevels || [], + levelStars: parsed.levelStars || {}, + levelBestMoves: parsed.levelBestMoves || {}, + }, + needsMigrationModal: false, }; } - } catch (e) { + } catch (_) { // Corrupted data, reset } - return { ...DEFAULT_PROGRESS }; + return { progress: { ...DEFAULT_PROGRESS }, needsMigrationModal: false }; +} + +/** Legacy compat shim — returns progress only. Use loadProgress() for migration detection. */ +export function getProgress() { + return loadProgress().progress; +} + +/** Whether the v2 migration modal has been acknowledged. */ +export function isMigrationAcknowledged() { + try { + return localStorage.getItem(MIGRATION_KEY) === 'done'; + } catch (_) { + return true; + } +} + +export function acknowledgeMigration() { + try { + localStorage.setItem(MIGRATION_KEY, 'done'); + } catch (_) { + // ignore + } } // Calculate star rating: 3★ = ≤par, 2★ = par+1 to par+3, 1★ = completed diff --git a/src/scenes/GameOver.svelte b/src/scenes/GameOver.svelte index 2ba2e2a..b75ed91 100644 --- a/src/scenes/GameOver.svelte +++ b/src/scenes/GameOver.svelte @@ -1,21 +1,24 @@
-

{title}

+

{title}

{desc}

-
@@ -29,7 +32,7 @@ } h1 { font: bold 48px Arial, sans-serif; } h1.accent { color: var(--text-accent); } - h1.danger { color: var(--text-danger); } + h1.bittersweet { color: var(--text-danger); } p { font: var(--font-body); color: var(--text-secondary); diff --git a/src/scenes/LevelSelect.svelte b/src/scenes/LevelSelect.svelte index 0cb0999..1aac461 100644 --- a/src/scenes/LevelSelect.svelte +++ b/src/scenes/LevelSelect.svelte @@ -10,7 +10,7 @@ function selectLevel(num) { if (num <= progress.maxLevel) { - navigate('LevelIntro', { level: num, lives: 3 }); + navigate('LevelIntro', { level: num }); } } diff --git a/src/scenes/StoryIntro.svelte b/src/scenes/StoryIntro.svelte index b1e9f92..d56a7c6 100644 --- a/src/scenes/StoryIntro.svelte +++ b/src/scenes/StoryIntro.svelte @@ -9,13 +9,13 @@ const el = document.querySelector('.story-scroll'); if (el) { el.addEventListener('animationend', () => { - if (!scrollDone) { scrollDone = true; navigate('LevelIntro', { level: 1, lives: 3 }); } + if (!scrollDone) { scrollDone = true; navigate('LevelIntro', { level: 1 }); } }); } }); function skip() { - if (!scrollDone) { scrollDone = true; navigate('LevelIntro', { level: 1, lives: 3 }); } + if (!scrollDone) { scrollDone = true; navigate('LevelIntro', { level: 1 }); } }