diff --git a/public/assets/generated/guard-blueberry.png b/public/assets/generated/guard-blueberry.png new file mode 100644 index 0000000..f917a54 Binary files /dev/null and b/public/assets/generated/guard-blueberry.png differ diff --git a/public/assets/generated/guard-corn.png b/public/assets/generated/guard-corn.png new file mode 100644 index 0000000..3ea8c25 Binary files /dev/null and b/public/assets/generated/guard-corn.png differ diff --git a/public/assets/generated/guard-eggplant.png b/public/assets/generated/guard-eggplant.png new file mode 100644 index 0000000..959d13a Binary files /dev/null and b/public/assets/generated/guard-eggplant.png differ diff --git a/public/assets/generated/guard-lettuce.png b/public/assets/generated/guard-lettuce.png new file mode 100644 index 0000000..9acbd0e Binary files /dev/null and b/public/assets/generated/guard-lettuce.png differ diff --git a/public/assets/generated/guard-pumpkin.png b/public/assets/generated/guard-pumpkin.png new file mode 100644 index 0000000..1fd2228 Binary files /dev/null and b/public/assets/generated/guard-pumpkin.png differ diff --git a/public/assets/generated/guard-sniper.png b/public/assets/generated/guard-sniper.png new file mode 100644 index 0000000..237b03b Binary files /dev/null and b/public/assets/generated/guard-sniper.png differ diff --git a/public/assets/generated/guard-suspicion-alert.png b/public/assets/generated/guard-suspicion-alert.png new file mode 100644 index 0000000..20bbddb Binary files /dev/null and b/public/assets/generated/guard-suspicion-alert.png differ diff --git a/public/assets/generated/guard-suspicion-calm.png b/public/assets/generated/guard-suspicion-calm.png new file mode 100644 index 0000000..c02bf11 Binary files /dev/null and b/public/assets/generated/guard-suspicion-calm.png differ diff --git a/public/assets/generated/guard-suspicion-fire.png b/public/assets/generated/guard-suspicion-fire.png new file mode 100644 index 0000000..e9db79c Binary files /dev/null and b/public/assets/generated/guard-suspicion-fire.png differ diff --git a/public/assets/generated/guard-tomato.png b/public/assets/generated/guard-tomato.png new file mode 100644 index 0000000..84173b0 Binary files /dev/null and b/public/assets/generated/guard-tomato.png differ diff --git a/public/assets/generated/icon-key-copper.png b/public/assets/generated/icon-key-copper.png new file mode 100644 index 0000000..744ecca Binary files /dev/null and b/public/assets/generated/icon-key-copper.png differ diff --git a/public/assets/generated/icon-key-gold.png b/public/assets/generated/icon-key-gold.png new file mode 100644 index 0000000..67f7f2f Binary files /dev/null and b/public/assets/generated/icon-key-gold.png differ diff --git a/public/assets/generated/icon-key-silver.png b/public/assets/generated/icon-key-silver.png new file mode 100644 index 0000000..4f59bb7 Binary files /dev/null and b/public/assets/generated/icon-key-silver.png differ diff --git a/public/assets/generated/icon-stone.png b/public/assets/generated/icon-stone.png new file mode 100644 index 0000000..192aa86 Binary files /dev/null and b/public/assets/generated/icon-stone.png differ diff --git a/public/assets/generated/nntv-generated-sheet.png b/public/assets/generated/nntv-generated-sheet.png new file mode 100644 index 0000000..b651372 Binary files /dev/null and b/public/assets/generated/nntv-generated-sheet.png differ diff --git a/public/assets/generated/player-rabbit.png b/public/assets/generated/player-rabbit.png new file mode 100644 index 0000000..c876a08 Binary files /dev/null and b/public/assets/generated/player-rabbit.png differ diff --git a/public/assets/generated/princess-carrot.png b/public/assets/generated/princess-carrot.png new file mode 100644 index 0000000..dc101b8 Binary files /dev/null and b/public/assets/generated/princess-carrot.png differ diff --git a/public/assets/generated/scene-chamber.png b/public/assets/generated/scene-chamber.png new file mode 100644 index 0000000..a9420a7 Binary files /dev/null and b/public/assets/generated/scene-chamber.png differ diff --git a/public/assets/generated/scene-fortress.png b/public/assets/generated/scene-fortress.png new file mode 100644 index 0000000..2dc636d Binary files /dev/null and b/public/assets/generated/scene-fortress.png differ diff --git a/public/assets/generated/scene-garden.png b/public/assets/generated/scene-garden.png new file mode 100644 index 0000000..681b390 Binary files /dev/null and b/public/assets/generated/scene-garden.png differ diff --git a/public/assets/generated/scene-palace.png b/public/assets/generated/scene-palace.png new file mode 100644 index 0000000..31ed22e Binary files /dev/null and b/public/assets/generated/scene-palace.png differ diff --git a/public/assets/generated/scene-underground.png b/public/assets/generated/scene-underground.png new file mode 100644 index 0000000..5db525f Binary files /dev/null and b/public/assets/generated/scene-underground.png differ diff --git a/public/assets/generated/scene-walls.png b/public/assets/generated/scene-walls.png new file mode 100644 index 0000000..20c7601 Binary files /dev/null and b/public/assets/generated/scene-walls.png differ diff --git a/public/assets/generated/tile-door-copper.png b/public/assets/generated/tile-door-copper.png new file mode 100644 index 0000000..b3d6b0b Binary files /dev/null and b/public/assets/generated/tile-door-copper.png differ diff --git a/public/assets/generated/tile-door-gold.png b/public/assets/generated/tile-door-gold.png new file mode 100644 index 0000000..f645262 Binary files /dev/null and b/public/assets/generated/tile-door-gold.png differ diff --git a/public/assets/generated/tile-door-silver.png b/public/assets/generated/tile-door-silver.png new file mode 100644 index 0000000..3e22861 Binary files /dev/null and b/public/assets/generated/tile-door-silver.png differ diff --git a/public/assets/generated/tile-empty.png b/public/assets/generated/tile-empty.png new file mode 100644 index 0000000..87d9aaf Binary files /dev/null and b/public/assets/generated/tile-empty.png differ diff --git a/public/assets/generated/tile-goal.png b/public/assets/generated/tile-goal.png new file mode 100644 index 0000000..e224e06 Binary files /dev/null and b/public/assets/generated/tile-goal.png differ diff --git a/public/assets/generated/tile-lit.png b/public/assets/generated/tile-lit.png new file mode 100644 index 0000000..c7a57bb Binary files /dev/null and b/public/assets/generated/tile-lit.png differ diff --git a/public/assets/generated/tile-mirror.png b/public/assets/generated/tile-mirror.png new file mode 100644 index 0000000..f98b5f7 Binary files /dev/null and b/public/assets/generated/tile-mirror.png differ diff --git a/public/assets/generated/tile-oneway.png b/public/assets/generated/tile-oneway.png new file mode 100644 index 0000000..da28a75 Binary files /dev/null and b/public/assets/generated/tile-oneway.png differ diff --git a/public/assets/generated/tile-wall.png b/public/assets/generated/tile-wall.png new file mode 100644 index 0000000..77c8dd4 Binary files /dev/null and b/public/assets/generated/tile-wall.png differ diff --git a/public/assets/generated/tile-warm.png b/public/assets/generated/tile-warm.png new file mode 100644 index 0000000..d5b238c Binary files /dev/null and b/public/assets/generated/tile-warm.png differ diff --git a/src/components/GameBoard.svelte b/src/components/GameBoard.svelte index f3d27de..a2b3afa 100644 --- a/src/components/GameBoard.svelte +++ b/src/components/GameBoard.svelte @@ -1,17 +1,8 @@ @@ -69,7 +59,7 @@ style="grid-template-columns: repeat({cols}, {cellSize}px); grid-template-rows: repeat({rows}, {cellSize}px);" > {#each cells as cell (cell.row * cols + cell.col)} - {@const tile = tileFor(cell)} + {@const tileSrc = tileSrcFor(cell)}
oncellclick?.(cell.row, cell.col)} > - + {#if previewCells.has(`${cell.row},${cell.col}`) && !cell.isLight}
- -
- {/if} - - - {#if cell.isDoor} - - {/if} - - - {#if cell.isKey} - - {/if} - - - {#if cell.isOneWay} - {/if} {#if cell.isWarm && !cell.isLight} - + + {/if} + + + {#if cell.isOneWay} + + {/if} + + + {#if cell.isDoor} + + {/if} + + + {#if cell.isKey} + {/if} @@ -149,6 +142,28 @@ cursor: pointer; } .cell.wall { cursor: default; } + .cell.lit .tile-image { + animation: lit-tile-pulse 900ms steps(3, end) infinite; + } + .cell.warm .tile-image { + animation: warm-tile-breathe 1.2s steps(3, end) infinite; + } + .cell.door-cell .door-overlay { + animation: door-lock-glint 1.7s steps(4, end) infinite; + } + .cell.key-cell .key-overlay { + animation: key-bob 1s steps(3, end) infinite; + } + .cell.oneway-cell .oneway-overlay { + animation: oneway-nudge 1.1s steps(3, end) infinite; + } + .tile-image { + width: 100%; + height: 100%; + object-fit: cover; + image-rendering: pixelated; + display: block; + } /* Preview ghost overlay */ .preview-overlay { @@ -157,6 +172,18 @@ pointer-events: none; opacity: 0.7; } + .preview-corners { + position: absolute; + inset: 4px; + border: 3px solid rgba(255, 234, 0, 0.65); + clip-path: polygon( + 0 0, 28% 0, 28% 8%, 8% 8%, 8% 28%, 0 28%, + 0 72%, 8% 72%, 8% 92%, 28% 92%, 28% 100%, 0 100%, + 72% 100%, 72% 92%, 92% 92%, 92% 72%, 100% 72%, + 100% 28%, 92% 28%, 92% 8%, 72% 8%, 72% 0, 100% 0, + 100% 100%, 0 100% + ); + } /* Detection flash animation */ .cell.detected-flash { @@ -166,64 +193,58 @@ 0% { filter: brightness(2) hue-rotate(-60deg); } 100% { filter: none; } } - - /* ── Door overlay ── */ - .door-overlay { - position: absolute; - inset: 3px; - border: 3px solid; /* color set inline */ - border-radius: 3px; - pointer-events: none; - display: flex; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.35); + @keyframes lit-tile-pulse { + 0%, 100% { filter: brightness(1); } + 50% { filter: brightness(1.22) saturate(1.1); } } - .door-glyph { - font-size: 1.1em; - line-height: 1; - filter: drop-shadow(0 1px 2px rgba(0,0,0,0.8)); + @keyframes warm-tile-breathe { + 0%, 100% { opacity: 0.82; } + 50% { opacity: 1; } + } + @keyframes door-lock-glint { + 0%, 100% { filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.75)) brightness(1); } + 50% { filter: drop-shadow(0 0 5px rgba(255, 244, 214, 0.35)) brightness(1.08); } + } + @keyframes key-bob { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-3px); } + } + @keyframes oneway-nudge { + 0%, 100% { translate: 0 0; opacity: 0.82; } + 50% { translate: 2px 0; opacity: 1; } } - /* ── Key overlay ── */ - .key-overlay { + .tile-overlay { position: absolute; inset: 0; pointer-events: none; - display: flex; - align-items: center; - justify-content: center; - } - .key-glyph { - font-size: 1em; - line-height: 1; - filter: drop-shadow(0 1px 3px rgba(0,0,0,0.9)); + transform-origin: center; } - /* ── One-way overlay ── */ - .oneway-overlay { - position: absolute; - inset: 0; - pointer-events: none; - display: flex; - align-items: center; - justify-content: center; - background: rgba(100, 180, 255, 0.08); - } - .oneway-arrow { - font-size: 1.3em; - color: rgba(140, 210, 255, 0.75); - line-height: 1; - text-shadow: 0 0 4px rgba(0,0,0,0.8); - } - - /* ── Warm cell overlay (distinct orange from yellow isLight) ── */ .warm-overlay { - position: absolute; - inset: 0; - pointer-events: none; - background: rgba(255, 140, 0, 0.25); - border-radius: 1px; + opacity: 0.9; + mix-blend-mode: screen; + } + + .oneway-overlay { + opacity: 0.82; + } + + .door-overlay { + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.75)); + } + + .key-overlay { + display: flex; + align-items: center; + justify-content: center; + filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.9)); + } + .key-image { + width: 76%; + height: 76%; + object-fit: contain; + image-rendering: pixelated; } /* ── Throw targeting rings ── */ @@ -248,4 +269,12 @@ from { opacity: 0.7; } to { opacity: 1.0; } } + @media (prefers-reduced-motion: reduce) { + .cell .tile-image, + .tile-overlay, + .key-overlay, + .throw-cursor-ring { + animation: none !important; + } + } diff --git a/src/components/GuardSprite.svelte b/src/components/GuardSprite.svelte index 0d60674..12159ab 100644 --- a/src/components/GuardSprite.svelte +++ b/src/components/GuardSprite.svelte @@ -1,7 +1,6 @@
- +
+ +
diff --git a/src/components/StonesCounter.svelte b/src/components/StonesCounter.svelte index 697466e..3120e8f 100644 --- a/src/components/StonesCounter.svelte +++ b/src/components/StonesCounter.svelte @@ -1,10 +1,8 @@
+ + {#if !isRunComplete} + + {/if}

{title}

{desc}

@@ -29,6 +34,25 @@ justify-content: center; align-items: center; gap: 24px; background: var(--bg-dark); + position: relative; + overflow: hidden; + } + .gameover-bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0.28; + image-rendering: pixelated; + } + .princess { + width: 108px; + height: 108px; + object-fit: contain; + image-rendering: pixelated; + filter: drop-shadow(0 0 18px rgba(255, 234, 0, 0.55)); + z-index: 1; } h1 { font: bold 48px Arial, sans-serif; } h1.accent { color: var(--text-accent); } @@ -39,6 +63,8 @@ text-align: center; max-width: 70%; white-space: pre-line; + z-index: 1; } - .buttons { display: flex; flex-direction: column; gap: 12px; } + h1 { z-index: 1; } + .buttons { display: flex; flex-direction: column; gap: 12px; z-index: 1; } diff --git a/src/scenes/LevelIntro.svelte b/src/scenes/LevelIntro.svelte index 4c28dc1..b8030db 100644 --- a/src/scenes/LevelIntro.svelte +++ b/src/scenes/LevelIntro.svelte @@ -3,14 +3,13 @@ import { LEVELS } from '../lib/levels/levels.js'; import Button from '../components/Button.svelte'; import AffordanceBanner from '../components/AffordanceBanner.svelte'; - import Pixel from '../lib/pixel/Pixel.svelte'; - import { sceneForLevel } from '../lib/pixel/art-scenes.js'; + import { generatedSceneForLevel } from '../lib/generated-assets.js'; let { navigate, level = 1 } = $props(); let levelData = $derived(LEVELS[level - 1]); let levelName = $derived(levelData?.name || `Level ${level}`); let storyText = $derived(levelData?.storyKey ? getText(levelData.storyKey) : ''); - let scene = $derived(sceneForLevel(level)); + let scene = $derived(generatedSceneForLevel(level)); // Affordance gates — default both enabled until phase 04 populates levels.js let affordances = $derived(levelData?.affordances ?? { undo: true, preview: true }); @@ -18,7 +17,7 @@
- +
{getText('level')}{level} @@ -47,6 +46,12 @@ align-items: center; justify-content: center; } + .backdrop img { + width: 100%; + height: 100%; + object-fit: cover; + image-rendering: pixelated; + } .foreground { position: relative; width: 100%; height: 100%; diff --git a/src/scenes/MainMenu.svelte b/src/scenes/MainMenu.svelte index 30a9fca..3035d9d 100644 --- a/src/scenes/MainMenu.svelte +++ b/src/scenes/MainMenu.svelte @@ -3,7 +3,7 @@ import Button from '../components/Button.svelte'; import Pixel from '../lib/pixel/Pixel.svelte'; import { MOON_ART, MOON_PAL } from '../lib/pixel/art-ui.js'; - import { RABBIT_ART, RABBIT_PAL } from '../lib/pixel/art-characters.js'; + import { GENERATED_CHARACTERS } from '../lib/generated-assets.js'; let { navigate } = $props(); const stars = Array.from({ length: 40 }, () => ({ @@ -28,7 +28,7 @@

{getText('gameTitle')}

- +
@@ -71,6 +71,12 @@ z-index: 1; filter: drop-shadow(0 4px 12px rgba(83, 52, 131, 0.6)); } + .hero img { + width: 108px; + height: 108px; + object-fit: contain; + image-rendering: pixelated; + } .buttons { display: flex; flex-direction: column; gap: 12px; z-index: 1;