Compare commits
3 Commits
d4b3a3842c
...
6fbac92d71
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fbac92d71 | |||
| 6f173e93c9 | |||
| 0253789ea4 |
@@ -1,5 +1,8 @@
|
||||
**Name**: Samantha Lopez
|
||||
|
||||
**ID**: 20266142
|
||||
|
||||
**Email**: samantha@kaist.ac.kr
|
||||
|
||||
**Gittea Repo**: [https://git.prototyping.id/20266142/The-Full-Hue](https://git.prototyping.id/20266142/The-Full-Hue)
|
||||
|
||||
BIN
public/backgrounds/Level10.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/backgrounds/Level10_complete.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/backgrounds/Level1_complete.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
public/backgrounds/Level2_complete.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
public/backgrounds/Level3_complete.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
public/backgrounds/Level4_complete.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 70 KiB |
BIN
public/backgrounds/Level5_complete.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/backgrounds/Level6.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/backgrounds/Level6_complete.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
public/backgrounds/Level7.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/backgrounds/Level7_complete.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/backgrounds/Level8.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/backgrounds/Level8_complete.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
public/backgrounds/Level9.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/backgrounds/Level9_complete.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
@@ -2,7 +2,8 @@
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { Player } from '../game/Player.js';
|
||||
import { get } from 'svelte/store';
|
||||
import { lives, colorOpacity, levelColor, onFragmentCollection, resetLevel, completeLevel } from '../stores/colorStore.js';
|
||||
import { lives, colorOpacity, levelColor, onFragmentCollection, resetLevel, completeLevel, gameCompleted } from '../stores/colorStore.js';
|
||||
import { showFragmentQuote, showCompleteQuote } from '../stores/quoteStore.js';
|
||||
import { push } from 'svelte-spa-router';
|
||||
import { Enemy } from '../game/Enemy.js';
|
||||
import { TarPuddle } from '../game/TarPuddle.js';
|
||||
@@ -18,6 +19,7 @@
|
||||
const keysDown = { left: false, right: false, jump: false };
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
if (e.key === 'Escape') { push('/levelselect'); return; }
|
||||
if (e.key === 'ArrowLeft' || e.key === 'a') { keysDown.left = true; e.preventDefault(); }
|
||||
if (e.key === 'ArrowRight' || e.key === 'd') { keysDown.right = true; e.preventDefault(); }
|
||||
// e.repeat blocks the browser auto-repeat from re-queuing a jump while held
|
||||
@@ -60,26 +62,37 @@
|
||||
let playerImgLoaded;
|
||||
let splatImg;
|
||||
|
||||
// fragment collection callback
|
||||
// defined here at sketch level so both setup and draw can see it
|
||||
const LAST_LEVEL = 10;
|
||||
let collectedCount = 0; // tracks which quote to show next
|
||||
|
||||
function onFragmentCollected(hexColor, x, y) {
|
||||
player.collectFragment(hexColor);
|
||||
onFragmentCollection(fragments.length, hexColor); // update the color store
|
||||
splats.push({ x, y, alpha: 200, size: 30 }); // add paint splash
|
||||
if (fragments.every(f => f.collected)) {
|
||||
onFragmentCollection(fragments.length, hexColor);
|
||||
splats.push({ x, y, alpha: 200, size: 30 });
|
||||
|
||||
const isLast = fragments.every(f => f.collected);
|
||||
|
||||
// skip the toast on the last fragment — the level-complete overlay takes over
|
||||
if (!isLast) {
|
||||
showFragmentQuote(levelData.fragmentQuotes?.[collectedCount], hexColor);
|
||||
}
|
||||
collectedCount++;
|
||||
|
||||
if (isLast) {
|
||||
gameState = 'levelcomplete';
|
||||
completeLevel(levelData.color);
|
||||
const dest = levelNumber === LAST_LEVEL ? '/win' : '/levelselect';
|
||||
setTimeout(() => push(dest), 2500);
|
||||
const dest = levelNumber === LAST_LEVEL ? '/win' : `/game?level=${levelNumber + 1}`;
|
||||
showCompleteQuote(levelData.completeQuote, levelData.color, dest);
|
||||
}
|
||||
}
|
||||
|
||||
p.preload = () => {
|
||||
const data = getLevel(levelNumber);
|
||||
if (data?.bg) bgImg = p.loadImage(data.bg);
|
||||
if (data?.playerImg) playerImgLoaded = p.loadImage(data.playerImg);
|
||||
const isComplete = get(gameCompleted);
|
||||
const bgPath = isComplete && data?.bgComplete ? data.bgComplete : data?.bg;
|
||||
if (bgPath) bgImg = p.loadImage(bgPath);
|
||||
const playerPath = isComplete ? '/assets/player_level10.png' : data?.playerImg;
|
||||
if (playerPath) playerImgLoaded = p.loadImage(playerPath);
|
||||
splatImg = p.loadImage('/assets/splat.png');
|
||||
};
|
||||
|
||||
@@ -149,20 +162,20 @@
|
||||
p.allSprites.update();
|
||||
|
||||
// ── 2. COLOR TINT OVERLAY ─────────────────────────────────────
|
||||
// as fragments are collected, the world gradually gains color
|
||||
// reveal goes from 0.0 (gray) to 1.0 (full color)
|
||||
// skipped after game completion — full-color backgrounds need no tint
|
||||
if (!get(gameCompleted)) {
|
||||
const reveal = get(colorOpacity);
|
||||
const hex = get(levelColor);
|
||||
if (reveal > 0 && hex) {
|
||||
const col = p.color(hex);
|
||||
// lerp blends between gray (128) and the level color
|
||||
const r = p.lerp(128, p.red(col), reveal);
|
||||
const g = p.lerp(128, p.green(col), reveal);
|
||||
const b = p.lerp(128, p.blue(col), reveal);
|
||||
p.noStroke();
|
||||
p.fill(r, g, b, reveal * 120); // max alpha 120 so its a subtle tint
|
||||
p.fill(r, g, b, reveal * 120);
|
||||
p.rect(0, 0, p.width, p.height);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. PLAYER INPUT ───────────────────────────────────────────
|
||||
// update runs AFTER allSprites.update() so touching.bottom is accurate
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<script>
|
||||
import {lives, fragmentsCollected, levelColor} from '../stores/colorStore.js'
|
||||
import { LEVELS } from '../game/levelData.js'
|
||||
import { lives, fragmentsCollected, levelColor } from '../stores/colorStore.js';
|
||||
import { LEVELS } from '../game/levelData.js';
|
||||
|
||||
export let levelNumber = 1;
|
||||
|
||||
// get total fragments for the level
|
||||
$: totalFragments = LEVELS.find(l => l.id === levelNumber)?.fragments.length ?? 0;
|
||||
</script>
|
||||
|
||||
<div class="hud">
|
||||
<!-- lives counter displayed on the left-->
|
||||
<div class = "lives">
|
||||
<div class="lives">
|
||||
{#each {length: 3} as _, i}
|
||||
<img
|
||||
src={i < $lives ? '/assets/heart_full.png' : '/assets/heart_empty.png'}
|
||||
@@ -21,7 +19,6 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- fragment collections shown on the right-->
|
||||
<div class="frags" style="color: {$levelColor}">
|
||||
{$fragmentsCollected} / {totalFragments}
|
||||
</div>
|
||||
@@ -36,8 +33,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
pointer-events: none; /* this allows the clicks to pass through to the game */
|
||||
z-index: 10; /* shows on top */
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.lives {
|
||||
@@ -45,10 +42,11 @@
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.frags{
|
||||
.frags {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
font-family:'Courier New', Courier, monospace;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
text-shadow: 0 1px 4px rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
</style>
|
||||
76
src/components/LevelCompleteOverlay.svelte
Normal file
@@ -0,0 +1,76 @@
|
||||
<script>
|
||||
import { push } from 'svelte-spa-router';
|
||||
import { completeQuoteData, clearCompleteQuote } from '../stores/quoteStore.js';
|
||||
|
||||
function proceed() {
|
||||
const dest = $completeQuoteData?.nextDest;
|
||||
clearCompleteQuote();
|
||||
if (dest) push(dest);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $completeQuoteData}
|
||||
<div class="overlay">
|
||||
<div class="bar" style="background: {$completeQuoteData.color}"></div>
|
||||
<p class="quote">{$completeQuoteData.text}</p>
|
||||
<button
|
||||
class="continue"
|
||||
style="border-color: {$completeQuoteData.color}; color: {$completeQuoteData.color}"
|
||||
on:click={proceed}
|
||||
>
|
||||
{$completeQuoteData.nextDest === '/win' ? 'finish →' : 'next level →'}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(6, 6, 6, 0.85);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
padding: 0 80px;
|
||||
z-index: 30;
|
||||
animation: fadeIn 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.quote {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 17px;
|
||||
color: #ddd;
|
||||
text-align: center;
|
||||
line-height: 1.75;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.continue {
|
||||
margin-top: 8px;
|
||||
padding: 9px 30px;
|
||||
background: transparent;
|
||||
border: 1px solid;
|
||||
border-radius: 22px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.continue:hover {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
</style>
|
||||
40
src/components/QuoteToast.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script>
|
||||
import { fragmentQuote } from '../stores/quoteStore.js';
|
||||
</script>
|
||||
|
||||
{#if $fragmentQuote}
|
||||
{#key $fragmentQuote.key}
|
||||
<div
|
||||
class="toast"
|
||||
style="border-color: {$fragmentQuote.color}; --accent: {$fragmentQuote.color}"
|
||||
>
|
||||
<p>{$fragmentQuote.text}</p>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.toast {
|
||||
position: absolute;
|
||||
bottom: 22px;
|
||||
right: 18px;
|
||||
width: 230px;
|
||||
padding: 10px 13px;
|
||||
background: rgba(8, 8, 8, 0.88);
|
||||
border-left: 3px solid;
|
||||
color: #ccc;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.55;
|
||||
pointer-events: none;
|
||||
animation: toastIn 4.2s ease forwards;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@keyframes toastIn {
|
||||
0% { opacity: 0; transform: translateX(14px); }
|
||||
12% { opacity: 1; transform: translateX(0); }
|
||||
78% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
</style>
|
||||
@@ -43,6 +43,16 @@ export class Player {
|
||||
keysDown.jump = false; // consume so it can't re-fire until the next keydown
|
||||
}
|
||||
|
||||
// clamp to canvas left/right edges
|
||||
const halfW = this.sprite.w / 2;
|
||||
if (this.sprite.x < halfW) {
|
||||
this.sprite.x = halfW;
|
||||
this.sprite.vel.x = 0;
|
||||
} else if (this.sprite.x > this.p.width - halfW) {
|
||||
this.sprite.x = this.p.width - halfW;
|
||||
this.sprite.vel.x = 0;
|
||||
}
|
||||
|
||||
// count down invincibility frames
|
||||
if (this.isInvincible) {
|
||||
this.invincibleTimer--;
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
// level config — one entry per level
|
||||
// x,y = center w,h = dimensions
|
||||
// fragment colors match the level color (updated hex values)
|
||||
// bg → put your PNG in public/backgrounds/levelN.png
|
||||
// playerImg → put your PNG in public/assets/player_levelN.png
|
||||
|
||||
export const LEVELS = [
|
||||
|
||||
// ── LEVEL 1: CRIMSON ──────────────────────────────────────────────────────
|
||||
// platforms and enemies unchanged from original design
|
||||
{
|
||||
id: 1,
|
||||
name: 'Eruption',
|
||||
name: 'A Burning Heart',
|
||||
color: '#970505',
|
||||
bg: '/backgrounds/level1.png',
|
||||
bgComplete: '/backgrounds/level1_complete.png',
|
||||
playerImg: '/assets/player_level1.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 }, // ground
|
||||
{ x: 170, y: 370, w: 160, h: 14 }, // left starter
|
||||
{ x: 390, y: 308, w: 150, h: 14 }, // middle step
|
||||
{ x: 610, y: 248, w: 150, h: 14 }, // upper right
|
||||
{ x: 395, y: 188, w: 140, h: 14 }, // top center
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 170, y: 370, w: 160, h: 14 },
|
||||
{ x: 390, y: 308, w: 150, h: 14 },
|
||||
{ x: 610, y: 248, w: 150, h: 14 },
|
||||
{ x: 395, y: 188, w: 140, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 170, y: 338, color: '#970505' },
|
||||
@@ -33,26 +32,33 @@ export const LEVELS = [
|
||||
{ x: 390, y: 283, patrol: 50 },
|
||||
],
|
||||
tar: [],
|
||||
fragmentQuotes: [
|
||||
'Red is the first color you learn to see.',
|
||||
'You felt it first, before you had words for it',
|
||||
'To feel intensely is not weakness. It is aliveness.',
|
||||
'The heart has always beaten in red.',
|
||||
],
|
||||
completeQuote: 'Red is the color of being alive. It asks nothing of you except honesty. Red is rage, passion, urgency, and love; The most viceral emotion, it demands to be felt.',
|
||||
},
|
||||
|
||||
// ── LEVEL 2: AMBER ────────────────────────────────────────────────────────
|
||||
// platforms and enemies unchanged from original design
|
||||
{
|
||||
id: 2,
|
||||
name: 'Sunset',
|
||||
name: 'Warm Hands',
|
||||
color: '#CF8917',
|
||||
bg: '/backgrounds/level2.png',
|
||||
bgComplete: '/backgrounds/level2_complete.png',
|
||||
playerImg: '/assets/player_level2.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 160, y: 375, w: 150, h: 14 }, // left low
|
||||
{ x: 360, y: 318, w: 140, h: 14 }, // center
|
||||
{ x: 560, y: 260, w: 130, h: 14 }, // right mid
|
||||
{ x: 710, y: 330, w: 110, h: 14 }, // right island
|
||||
{ x: 280, y: 238, w: 110, h: 14 }, // upper left — backtrack
|
||||
{ x: 478, y: 188, w: 110, h: 14 }, // top center
|
||||
{ x: 160, y: 375, w: 150, h: 14 },
|
||||
{ x: 360, y: 318, w: 140, h: 14 },
|
||||
{ x: 560, y: 260, w: 130, h: 14 },
|
||||
{ x: 710, y: 330, w: 110, h: 14 },
|
||||
{ x: 280, y: 238, w: 110, h: 14 },
|
||||
{ x: 478, y: 188, w: 110, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 160, y: 343, color: '#CF8917' },
|
||||
@@ -67,33 +73,40 @@ export const LEVELS = [
|
||||
tar: [
|
||||
{ x: 490, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Not every fire burns, some just keep you warm',
|
||||
'Warmth is a form of courage.',
|
||||
'You made something today.That matters.',
|
||||
'You were built to connect and create.',
|
||||
],
|
||||
completeQuote: 'Orange reminds you that making things is an act of hope. It is the color of warmth, creativity, enthusiams, and connection. It is a choice to stay open. Let yourself be warm.',
|
||||
},
|
||||
|
||||
// ── LEVEL 3: YELLOW ───────────────────────────────────────────────────────
|
||||
// two wide wings — left and right — converging at the upper center
|
||||
{
|
||||
id: 3,
|
||||
name: 'Golden',
|
||||
name: 'A Bright Ache',
|
||||
color: '#E3D214',
|
||||
bg: '/backgrounds/level3.png',
|
||||
bgComplete: '/backgrounds/level3_complete.png',
|
||||
playerImg: '/assets/player_level3.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 140, y: 372, w: 150, h: 14 }, // left start
|
||||
{ x: 575, y: 365, w: 150, h: 14 }, // right start (enemy guards)
|
||||
{ x: 280, y: 302, w: 120, h: 14 }, // left mid
|
||||
{ x: 510, y: 295, w: 120, h: 14 }, // right mid
|
||||
{ x: 155, y: 238, w: 110, h: 14 }, // upper left
|
||||
{ x: 420, y: 228, w: 130, h: 14 }, // upper center (wider, enemy)
|
||||
{ x: 660, y: 220, w: 110, h: 14 }, // upper right
|
||||
{ x: 140, y: 372, w: 150, h: 14 },
|
||||
{ x: 575, y: 365, w: 150, h: 14 },
|
||||
{ x: 280, y: 302, w: 120, h: 14 },
|
||||
{ x: 510, y: 295, w: 120, h: 14 },
|
||||
{ x: 155, y: 238, w: 110, h: 14 },
|
||||
{ x: 420, y: 228, w: 130, h: 14 },
|
||||
{ x: 660, y: 220, w: 110, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 280, y: 270, color: '#E3D214' }, // left mid — easy
|
||||
{ x: 660, y: 188, color: '#E3D214' }, // upper right
|
||||
{ x: 155, y: 206, color: '#E3D214' }, // upper left
|
||||
{ x: 420, y: 196, color: '#E3D214' }, // upper center — guarded
|
||||
{ x: 280, y: 270, color: '#E3D214' },
|
||||
{ x: 660, y: 188, color: '#E3D214' },
|
||||
{ x: 155, y: 206, color: '#E3D214' },
|
||||
{ x: 420, y: 196, color: '#E3D214' },
|
||||
],
|
||||
enemies: [
|
||||
{ x: 575, y: 340, patrol: 52 },
|
||||
@@ -103,35 +116,42 @@ export const LEVELS = [
|
||||
{ x: 370, y: 432 },
|
||||
{ x: 700, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Joy is allowed to be simple',
|
||||
'Clarity costs something, it asks you to really look.',
|
||||
'Anxiety and curiosity live in the same color.',
|
||||
'Your mind runs fast because it cares deeply',
|
||||
],
|
||||
completeQuote: 'Yellow carries both hope and anxiety in equal measure. It is the color of joy but also of anxeity. Your nervous energy is not a flaw. It is the same thing as your intelligence, it goes hand in hand with your joy\'s .',
|
||||
},
|
||||
|
||||
// ── LEVEL 4: GREEN ────────────────────────────────────────────────────────
|
||||
// two vertical columns with connecting bridges — forest canopy feel
|
||||
{
|
||||
id: 4,
|
||||
name: 'Greenery',
|
||||
name: 'Growth',
|
||||
color: '#39BD1C',
|
||||
bg: '/backgrounds/level4.png',
|
||||
bgComplete: '/backgrounds/level4_complete.png',
|
||||
playerImg: '/assets/player_level4.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 140, y: 372, w: 130, h: 14 }, // left start (enemy guards)
|
||||
{ x: 665, y: 365, w: 130, h: 14 }, // right — separated by tar
|
||||
{ x: 270, y: 305, w: 120, h: 14 }, // center-left mid (enemy guards)
|
||||
{ x: 510, y: 298, w: 120, h: 14 }, // center-right mid
|
||||
{ x: 165, y: 242, w: 110, h: 14 }, // upper left
|
||||
{ x: 380, y: 232, w: 120, h: 14 }, // upper center
|
||||
{ x: 610, y: 225, w: 110, h: 14 }, // upper right (enemy guards)
|
||||
{ x: 290, y: 175, w: 95, h: 14 }, // top left
|
||||
{ x: 520, y: 168, w: 95, h: 14 }, // top right — hardest
|
||||
{ x: 140, y: 372, w: 130, h: 14 },
|
||||
{ x: 665, y: 365, w: 130, h: 14 },
|
||||
{ x: 270, y: 305, w: 120, h: 14 },
|
||||
{ x: 510, y: 298, w: 120, h: 14 },
|
||||
{ x: 165, y: 242, w: 110, h: 14 },
|
||||
{ x: 380, y: 232, w: 120, h: 14 },
|
||||
{ x: 610, y: 225, w: 110, h: 14 },
|
||||
{ x: 290, y: 175, w: 95, h: 14 },
|
||||
{ x: 520, y: 168, w: 95, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 270, y: 273, color: '#39BD1C' }, // center-left mid
|
||||
{ x: 510, y: 266, color: '#39BD1C' }, // center-right mid
|
||||
{ x: 380, y: 200, color: '#39BD1C' }, // upper center
|
||||
{ x: 520, y: 136, color: '#39BD1C' }, // top right — hardest
|
||||
{ x: 270, y: 273, color: '#39BD1C' },
|
||||
{ x: 510, y: 266, color: '#39BD1C' },
|
||||
{ x: 380, y: 200, color: '#39BD1C' },
|
||||
{ x: 520, y: 136, color: '#39BD1C' },
|
||||
],
|
||||
enemies: [
|
||||
{ x: 140, y: 347, patrol: 40 },
|
||||
@@ -142,38 +162,45 @@ export const LEVELS = [
|
||||
{ x: 395, y: 432 },
|
||||
{ x: 610, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Growth rarely feels like growth while it\'s happening.',
|
||||
'Green is the slowest and most stubborn color.',
|
||||
'Healing is not linear',
|
||||
'Every root is also a reach.',
|
||||
],
|
||||
completeQuote: 'Green is the color of becoming. It does not rush, does not announce itself. It doesn\'t ask you to heal, only to keep growing. Green represents growth, healing, balance, the slow work of becoming. You are allowed to grow quietly, at your own pace.',
|
||||
},
|
||||
|
||||
// ── LEVEL 5: CYAN ─────────────────────────────────────────────────────────
|
||||
// zigzag flow — dips and rises like tide pools
|
||||
{
|
||||
id: 5,
|
||||
name: 'Tidal',
|
||||
name: 'Open Water',
|
||||
color: '#12B6C8',
|
||||
bg: '/backgrounds/level5.png',
|
||||
bgComplete: '/backgrounds/level5_complete.png',
|
||||
playerImg: '/assets/player_level5.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 145, y: 378, w: 140, h: 14 }, // left start
|
||||
{ x: 355, y: 395, w: 115, h: 14 }, // dips down — zigzag
|
||||
{ x: 540, y: 368, w: 125, h: 14 }, // center right
|
||||
{ x: 705, y: 348, w: 115, h: 14 }, // far right
|
||||
{ x: 235, y: 308, w: 110, h: 14 }, // upper left
|
||||
{ x: 445, y: 295, w: 110, h: 14 }, // upper center (enemy)
|
||||
{ x: 640, y: 278, w: 115, h: 14 }, // upper right
|
||||
{ x: 125, y: 248, w: 95, h: 14 }, // high far left — isolated
|
||||
{ x: 365, y: 235, w: 90, h: 14 }, // high center
|
||||
{ x: 590, y: 220, w: 90, h: 14 }, // high right (enemy)
|
||||
{ x: 755, y: 202, w: 80, h: 14 }, // top far right — narrow
|
||||
{ x: 145, y: 378, w: 140, h: 14 },
|
||||
{ x: 355, y: 395, w: 115, h: 14 },
|
||||
{ x: 540, y: 368, w: 125, h: 14 },
|
||||
{ x: 705, y: 348, w: 115, h: 14 },
|
||||
{ x: 235, y: 308, w: 110, h: 14 },
|
||||
{ x: 445, y: 295, w: 110, h: 14 },
|
||||
{ x: 640, y: 278, w: 115, h: 14 },
|
||||
{ x: 125, y: 248, w: 95, h: 14 },
|
||||
{ x: 365, y: 235, w: 90, h: 14 },
|
||||
{ x: 590, y: 220, w: 90, h: 14 },
|
||||
{ x: 755, y: 202, w: 80, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 235, y: 276, color: '#12B6C8' }, // upper left
|
||||
{ x: 640, y: 246, color: '#12B6C8' }, // upper right
|
||||
{ x: 125, y: 216, color: '#12B6C8' }, // high far left — isolated
|
||||
{ x: 365, y: 203, color: '#12B6C8' }, // high center
|
||||
{ x: 755, y: 170, color: '#12B6C8' }, // top far right — hardest
|
||||
{ x: 235, y: 276, color: '#12B6C8' },
|
||||
{ x: 640, y: 246, color: '#12B6C8' },
|
||||
{ x: 125, y: 216, color: '#12B6C8' },
|
||||
{ x: 365, y: 203, color: '#12B6C8' },
|
||||
{ x: 755, y: 170, color: '#12B6C8' },
|
||||
],
|
||||
enemies: [
|
||||
{ x: 355, y: 370, patrol: 35 },
|
||||
@@ -185,40 +212,48 @@ export const LEVELS = [
|
||||
{ x: 480, y: 432 },
|
||||
{ x: 680, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Clarity comes after, not before',
|
||||
'Clear water still has a bottom.',
|
||||
'Calm is not the absence of feeling, it\'s feeling without drowning.',
|
||||
'To speak honestly is an act of trust.',
|
||||
'You can be still and still be powerful.',
|
||||
],
|
||||
completeQuote: 'Cyan is the color of honest water, clear enough to see through, deep enough to matter, and standing at a calming still. Cyan sits between calmness and clarity. It asks you to say what you mean, and listen with clarity. ',
|
||||
},
|
||||
|
||||
// ── LEVEL 6: DEEP BLUE ────────────────────────────────────────────────────
|
||||
// cold, sparse — wider gaps, deliberate platform placement
|
||||
{
|
||||
id: 6,
|
||||
name: 'The Abyss',
|
||||
name: 'A Long Quiet',
|
||||
color: '#170CB7',
|
||||
bg: '/backgrounds/level6.png',
|
||||
bgComplete: '/backgrounds/level6_complete.png',
|
||||
playerImg: '/assets/player_level6.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 130, y: 382, w: 125, h: 14 }, // left start
|
||||
{ x: 675, y: 370, w: 130, h: 14 }, // far right — requires commitment
|
||||
{ x: 308, y: 355, w: 108, h: 14 }, // center step
|
||||
{ x: 510, y: 385, w: 105, h: 14 }, // dip right
|
||||
{ x: 220, y: 302, w: 100, h: 14 }, // upper left
|
||||
{ x: 455, y: 290, w: 100, h: 14 }, // upper center
|
||||
{ x: 660, y: 275, w: 110, h: 14 }, // upper right (enemy)
|
||||
{ x: 105, y: 245, w: 88, h: 14 }, // high far left — isolated
|
||||
{ x: 358, y: 232, w: 85, h: 14 }, // high center
|
||||
{ x: 580, y: 215, w: 85, h: 14 }, // high right
|
||||
{ x: 745, y: 198, w: 80, h: 14 }, // narrow top right
|
||||
{ x: 268, y: 175, w: 80, h: 14 }, // top left
|
||||
{ x: 480, y: 165, w: 80, h: 14 }, // very top — hardest
|
||||
{ x: 130, y: 382, w: 125, h: 14 },
|
||||
{ x: 675, y: 370, w: 130, h: 14 },
|
||||
{ x: 308, y: 355, w: 108, h: 14 },
|
||||
{ x: 510, y: 385, w: 105, h: 14 },
|
||||
{ x: 220, y: 302, w: 100, h: 14 },
|
||||
{ x: 455, y: 290, w: 100, h: 14 },
|
||||
{ x: 660, y: 275, w: 110, h: 14 },
|
||||
{ x: 105, y: 245, w: 88, h: 14 },
|
||||
{ x: 358, y: 232, w: 85, h: 14 },
|
||||
{ x: 580, y: 215, w: 85, h: 14 },
|
||||
{ x: 745, y: 198, w: 80, h: 14 },
|
||||
{ x: 268, y: 175, w: 80, h: 14 },
|
||||
{ x: 480, y: 165, w: 80, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 308, y: 323, color: '#170CB7' }, // center step — warmup
|
||||
{ x: 220, y: 270, color: '#170CB7' }, // upper left
|
||||
{ x: 660, y: 243, color: '#170CB7' }, // upper right
|
||||
{ x: 358, y: 200, color: '#170CB7' }, // high center
|
||||
{ x: 480, y: 133, color: '#170CB7' }, // very top — hardest
|
||||
{ x: 308, y: 323, color: '#170CB7' },
|
||||
{ x: 220, y: 270, color: '#170CB7' },
|
||||
{ x: 660, y: 243, color: '#170CB7' },
|
||||
{ x: 358, y: 200, color: '#170CB7' },
|
||||
{ x: 480, y: 133, color: '#170CB7' },
|
||||
],
|
||||
enemies: [
|
||||
{ x: 510, y: 360, patrol: 33 },
|
||||
@@ -231,39 +266,47 @@ export const LEVELS = [
|
||||
{ x: 460, y: 432 },
|
||||
{ x: 640, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Some feelings don\'t have names, they just have weight',
|
||||
'There is beauty in melancholy, it means you loved something.',
|
||||
'Depth and darkness are not the same thing.',
|
||||
'You are allowed to sit with it.',
|
||||
'Some truths only surface in the quiet.',
|
||||
],
|
||||
completeQuote: 'Blue is the color of depth, sadness, and introspection, the most universally felt emotions. It does not ask you to feel better. It asks you to feel, that is enough. Do not be afraid to go deep. That is where you find out who you actually are.',
|
||||
},
|
||||
|
||||
// ── LEVEL 7: PURPLE ───────────────────────────────────────────────────────
|
||||
// narrow platforms spiraling up — precision required
|
||||
{
|
||||
id: 7,
|
||||
name: 'Twilight Spire',
|
||||
name: 'In-Between',
|
||||
color: '#6613BA',
|
||||
bg: '/backgrounds/level7.png',
|
||||
bgComplete: '/backgrounds/level7_complete.png',
|
||||
playerImg: '/assets/player_level7.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 130, y: 385, w: 115, h: 14 }, // left start
|
||||
{ x: 308, y: 365, w: 100, h: 14 }, // small step
|
||||
{ x: 483, y: 390, w: 100, h: 14 }, // dip
|
||||
{ x: 652, y: 358, w: 115, h: 14 }, // right mid
|
||||
{ x: 768, y: 300, w: 75, h: 14 }, // narrow far right
|
||||
{ x: 578, y: 272, w: 88, h: 14 }, // upper right
|
||||
{ x: 400, y: 295, w: 85, h: 14 }, // upper center
|
||||
{ x: 220, y: 285, w: 90, h: 14 }, // upper left
|
||||
{ x: 90, y: 248, w: 80, h: 14 }, // narrow far left
|
||||
{ x: 320, y: 232, w: 80, h: 14 }, // high center
|
||||
{ x: 518, y: 215, w: 80, h: 14 }, // high right
|
||||
{ x: 698, y: 190, w: 78, h: 14 }, // top right — narrow
|
||||
{ x: 130, y: 385, w: 115, h: 14 },
|
||||
{ x: 308, y: 365, w: 100, h: 14 },
|
||||
{ x: 483, y: 390, w: 100, h: 14 },
|
||||
{ x: 652, y: 358, w: 115, h: 14 },
|
||||
{ x: 768, y: 300, w: 75, h: 14 },
|
||||
{ x: 578, y: 272, w: 88, h: 14 },
|
||||
{ x: 400, y: 295, w: 85, h: 14 },
|
||||
{ x: 220, y: 285, w: 90, h: 14 },
|
||||
{ x: 90, y: 248, w: 80, h: 14 },
|
||||
{ x: 320, y: 232, w: 80, h: 14 },
|
||||
{ x: 518, y: 215, w: 80, h: 14 },
|
||||
{ x: 698, y: 190, w: 78, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 652, y: 326, color: '#6613BA' }, // right mid
|
||||
{ x: 220, y: 253, color: '#6613BA' }, // upper left
|
||||
{ x: 320, y: 200, color: '#6613BA' }, // high center
|
||||
{ x: 518, y: 183, color: '#6613BA' }, // high right
|
||||
{ x: 698, y: 158, color: '#6613BA' }, // top right — hardest
|
||||
{ x: 652, y: 326, color: '#6613BA' },
|
||||
{ x: 220, y: 253, color: '#6613BA' },
|
||||
{ x: 320, y: 200, color: '#6613BA' },
|
||||
{ x: 518, y: 183, color: '#6613BA' },
|
||||
{ x: 698, y: 158, color: '#6613BA' },
|
||||
],
|
||||
enemies: [
|
||||
{ x: 308, y: 340, patrol: 28 },
|
||||
@@ -276,15 +319,23 @@ export const LEVELS = [
|
||||
{ x: 408, y: 432 },
|
||||
{ x: 618, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Not everything needs an explanation.',
|
||||
'Mystery is an invitation, not a threat.',
|
||||
'Your contradictions are not flaws, ther are complexity.',
|
||||
'The unknown is not something to fix.',
|
||||
'Transformation is always a little uncomfortable.',
|
||||
],
|
||||
completeQuote: 'Purple is the color of the in-betweens it represents mystery and intuition. Purple lives in the questions. You do not need everything figured out. Some things are only ever felt, never fully explained.',
|
||||
},
|
||||
|
||||
// ── LEVEL 8: MAGENTA ──────────────────────────────────────────────────────
|
||||
// energetic grid-like layout with strategic gaps
|
||||
{
|
||||
id: 8,
|
||||
name: 'Neon Bloom',
|
||||
name: 'Tenderness',
|
||||
color: '#C71287',
|
||||
bg: '/backgrounds/level8.png',
|
||||
bgComplete: '/backgrounds/level8_complete.png',
|
||||
playerImg: '/assets/player_level8.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
@@ -292,24 +343,24 @@ export const LEVELS = [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 155, y: 380, w: 130, h: 14 },
|
||||
{ x: 338, y: 360, w: 118, h: 14 },
|
||||
{ x: 513, y: 380, w: 110, h: 14 }, // dip
|
||||
{ x: 513, y: 380, w: 110, h: 14 },
|
||||
{ x: 688, y: 358, w: 120, h: 14 },
|
||||
{ x: 165, y: 308, w: 100, h: 14 }, // upper left
|
||||
{ x: 365, y: 300, w: 90, h: 14 }, // upper center
|
||||
{ x: 553, y: 308, w: 100, h: 14 }, // upper right
|
||||
{ x: 728, y: 278, w: 78, h: 14 }, // narrow far right
|
||||
{ x: 260, y: 248, w: 90, h: 14 }, // high left
|
||||
{ x: 448, y: 238, w: 90, h: 14 }, // high center
|
||||
{ x: 636, y: 222, w: 90, h: 14 }, // high right
|
||||
{ x: 368, y: 178, w: 80, h: 14 }, // near top
|
||||
{ x: 553, y: 165, w: 80, h: 14 }, // top — hardest
|
||||
{ x: 165, y: 308, w: 100, h: 14 },
|
||||
{ x: 365, y: 300, w: 90, h: 14 },
|
||||
{ x: 553, y: 308, w: 100, h: 14 },
|
||||
{ x: 728, y: 278, w: 78, h: 14 },
|
||||
{ x: 260, y: 248, w: 90, h: 14 },
|
||||
{ x: 448, y: 238, w: 90, h: 14 },
|
||||
{ x: 636, y: 222, w: 90, h: 14 },
|
||||
{ x: 368, y: 178, w: 80, h: 14 },
|
||||
{ x: 553, y: 165, w: 80, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 165, y: 276, color: '#C71287' }, // upper left
|
||||
{ x: 728, y: 246, color: '#C71287' }, // narrow far right
|
||||
{ x: 448, y: 206, color: '#C71287' }, // high center
|
||||
{ x: 368, y: 146, color: '#C71287' }, // near top
|
||||
{ x: 553, y: 133, color: '#C71287' }, // top — hardest
|
||||
{ x: 165, y: 276, color: '#C71287' },
|
||||
{ x: 728, y: 246, color: '#C71287' },
|
||||
{ x: 448, y: 206, color: '#C71287' },
|
||||
{ x: 368, y: 146, color: '#C71287' },
|
||||
{ x: 553, y: 133, color: '#C71287' },
|
||||
],
|
||||
enemies: [
|
||||
{ x: 338, y: 335, patrol: 38 },
|
||||
@@ -323,15 +374,23 @@ export const LEVELS = [
|
||||
{ x: 558, y: 432 },
|
||||
{ x: 738, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Pink is strength dressed in softness.',
|
||||
'Compassion begins with yourself.',
|
||||
'You would never speak to a friend the way you speak to yourself.',
|
||||
'Playfulness is not childishness, it is aliveness.',
|
||||
'You are allowed to bloom loudly.',
|
||||
],
|
||||
completeQuote: 'Magenta doesn\'t apologize for being bright. Magenta represents compassion, self-love, and softness. It aks you to be as gentel with youself as you are with the people you love the most.',
|
||||
},
|
||||
|
||||
// ── LEVEL 9: BROWN ────────────────────────────────────────────────────────
|
||||
// dense cave — most enemies, hardest single-color level
|
||||
{
|
||||
id: 9,
|
||||
name: 'Deep Caves',
|
||||
name: 'What holds you',
|
||||
color: '#753F16',
|
||||
bg: '/backgrounds/level9.png',
|
||||
bgComplete: '/backgrounds/level9_complete.png',
|
||||
playerImg: '/assets/player_level9.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
@@ -339,12 +398,12 @@ export const LEVELS = [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
{ x: 135, y: 382, w: 128, h: 14 },
|
||||
{ x: 315, y: 365, w: 112, h: 14 },
|
||||
{ x: 496, y: 388, w: 108, h: 14 }, // dip
|
||||
{ x: 496, y: 388, w: 108, h: 14 },
|
||||
{ x: 660, y: 358, w: 118, h: 14 },
|
||||
{ x: 200, y: 322, w: 100, h: 14 },
|
||||
{ x: 400, y: 312, w: 93, h: 14 },
|
||||
{ x: 578, y: 305, w: 100, h: 14 },
|
||||
{ x: 738, y: 288, w: 78, h: 14 }, // narrow
|
||||
{ x: 738, y: 288, w: 78, h: 14 },
|
||||
{ x: 118, y: 262, w: 88, h: 14 },
|
||||
{ x: 310, y: 255, w: 88, h: 14 },
|
||||
{ x: 492, y: 245, w: 88, h: 14 },
|
||||
@@ -373,51 +432,54 @@ export const LEVELS = [
|
||||
{ x: 540, y: 432 },
|
||||
{ x: 712, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'Some things stay so that other things can move.',
|
||||
'Stability is essential.',
|
||||
'The earth holds everything without complaint.',
|
||||
'Steadiness is a kind of strenght.',
|
||||
'Your roors are not holding you back.',
|
||||
],
|
||||
completeQuote: 'Brown is groundedness and stability. It is the earth beneath everything, often overlooked but everything grows from it. It does not need to be seen to do its work.',
|
||||
},
|
||||
|
||||
// ── LEVEL 10: THE COLOR REALM — final ────────────────────────────────────
|
||||
// one fragment per level color — completing this leads to the win screen
|
||||
// ── LEVEL 10: THE COLOR REALM — final ─────────────────────────────────────
|
||||
{
|
||||
id: 10,
|
||||
name: 'The Color Realm',
|
||||
name: 'The Whole of You',
|
||||
color: '#FFD700',
|
||||
bg: '/backgrounds/level10.png',
|
||||
bgComplete: '/backgrounds/level10_complete.png',
|
||||
playerImg: '/assets/player_level10.png',
|
||||
spawnX: 60,
|
||||
spawnY: 400,
|
||||
platforms: [
|
||||
{ x: 400, y: 440, w: 800, h: 12 },
|
||||
// row 1 — low
|
||||
{ x: 120, y: 380, w: 140, h: 14 },
|
||||
{ x: 310, y: 362, w: 120, h: 14 },
|
||||
{ x: 510, y: 382, w: 120, h: 14 },
|
||||
{ x: 700, y: 365, w: 130, h: 14 },
|
||||
// row 2 — mid
|
||||
{ x: 210, y: 305, w: 120, h: 14 },
|
||||
{ x: 420, y: 298, w: 110, h: 14 },
|
||||
{ x: 628, y: 288, w: 120, h: 14 },
|
||||
// row 3 — high
|
||||
{ x: 115, y: 248, w: 100, h: 14 },
|
||||
{ x: 315, y: 238, w: 95, h: 14 },
|
||||
{ x: 515, y: 228, w: 95, h: 14 },
|
||||
{ x: 715, y: 215, w: 90, h: 14 },
|
||||
// row 4 — top
|
||||
{ x: 215, y: 178, w: 90, h: 14 },
|
||||
{ x: 415, y: 168, w: 90, h: 14 },
|
||||
{ x: 615, y: 160, w: 90, h: 14 },
|
||||
// row 5 — very top (brown fragment)
|
||||
{ x: 415, y: 128, w: 80, h: 14 },
|
||||
],
|
||||
fragments: [
|
||||
{ x: 310, y: 330, color: '#970505' }, // crimson
|
||||
{ x: 700, y: 333, color: '#CF8917' }, // amber
|
||||
{ x: 210, y: 273, color: '#E3D214' }, // yellow
|
||||
{ x: 628, y: 256, color: '#39BD1C' }, // green
|
||||
{ x: 315, y: 206, color: '#12B6C8' }, // cyan
|
||||
{ x: 715, y: 183, color: '#170CB7' }, // deep blue
|
||||
{ x: 215, y: 146, color: '#6613BA' }, // purple
|
||||
{ x: 615, y: 128, color: '#C71287' }, // magenta
|
||||
{ x: 415, y: 96, color: '#753F16' }, // brown — very top
|
||||
{ x: 310, y: 330, color: '#970505' },
|
||||
{ x: 700, y: 333, color: '#CF8917' },
|
||||
{ x: 210, y: 273, color: '#E3D214' },
|
||||
{ x: 628, y: 256, color: '#39BD1C' },
|
||||
{ x: 315, y: 206, color: '#12B6C8' },
|
||||
{ x: 715, y: 183, color: '#170CB7' },
|
||||
{ x: 215, y: 146, color: '#6613BA' },
|
||||
{ x: 615, y: 128, color: '#C71287' },
|
||||
{ x: 415, y: 96, color: '#753F16' },
|
||||
],
|
||||
enemies: [
|
||||
{ x: 120, y: 355, patrol: 45 },
|
||||
@@ -433,6 +495,18 @@ export const LEVELS = [
|
||||
{ x: 665, y: 432 },
|
||||
{ x: 760, y: 432 },
|
||||
],
|
||||
fragmentQuotes: [
|
||||
'you burned',
|
||||
'you made',
|
||||
'you hoped and worried',
|
||||
'you grew',
|
||||
'you let go',
|
||||
'you felt deeply',
|
||||
'you wondered',
|
||||
'you loved',
|
||||
'you stayed',
|
||||
],
|
||||
completeQuote: 'Every emotion is a part of you. You needed all of it. The rage and the tenderness. The confusion and the clarity. The grief and the joy. A world with only one color is not a world at all. Neither are you. You are the whole spectrum. The full Hue.',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { querystring } from 'svelte-spa-router';
|
||||
import GameCanvas from '../components/GameCanvas.svelte';
|
||||
import HUD from '../components/HUD.svelte';
|
||||
import QuoteToast from '../components/QuoteToast.svelte';
|
||||
import LevelCompleteOverlay from '../components/LevelCompleteOverlay.svelte';
|
||||
|
||||
// querystring is the part after ? in the URL e.g. "level=2"
|
||||
// we parse it safely — if missing, default to level 1
|
||||
@@ -11,8 +13,12 @@
|
||||
</script>
|
||||
|
||||
<div class="game-wrapper">
|
||||
{#key levelNum}
|
||||
<GameCanvas levelNumber={levelNum}/>
|
||||
{/key}
|
||||
<HUD levelNumber={levelNum}/>
|
||||
<QuoteToast />
|
||||
<LevelCompleteOverlay />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<!--<img src="/backgrounds/title_bg.png" class="bg" alt="title background"/>-->
|
||||
|
||||
<div class="content">
|
||||
<h1>ColorQuest</h1>
|
||||
<p>Bring color back to your world</p>
|
||||
<h1>The Full Hue</h1>
|
||||
<p>Bring back your color</p>
|
||||
<button on:click={startGame}>begin</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
<div class="screen">
|
||||
<h1 class="title">color restored</h1>
|
||||
<p class="line1">every fragment found. every hue returned to the world.</p>
|
||||
<p class="line2">the little painter has done it.</p>
|
||||
<p class="line1">every fragment found. every hue returned </p>
|
||||
<p class="line2">Go forth and expereince the world in full color.</p>
|
||||
<button on:click={() => push('/')}>back to home</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
|
||||
// -- so these are the global variables ----
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
import { writable, derived } from 'svelte/store';
|
||||
import { LEVELS } from '../game/levelData.js';
|
||||
|
||||
// world starts gray so unlockedColors start as an empty array (there is none)
|
||||
export const unlockedColors = writable([]);
|
||||
@@ -33,6 +34,12 @@ export const fragmentsCollected = writable(0);
|
||||
//player lives
|
||||
export const lives = writable(3);
|
||||
|
||||
// true once every level's color has been unlocked (game beaten)
|
||||
export const gameCompleted = derived(
|
||||
unlockedColors,
|
||||
$colors => LEVELS.every(level => $colors.includes(level.color))
|
||||
);
|
||||
|
||||
// --- these are the global functions -----
|
||||
|
||||
// for collecting a fragment
|
||||
|
||||
36
src/stores/quoteStore.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
// ── Fragment quote toast ──────────────────────────────────────────────────────
|
||||
// Shows a short sentence in the bottom-right when a fragment is collected.
|
||||
// Auto-clears after 4 seconds; cancels any previous timer so rapid collection
|
||||
// always shows the newest quote cleanly.
|
||||
|
||||
export const fragmentQuote = writable(null); // { text, color, key } | null
|
||||
|
||||
let toastTimer = null;
|
||||
let quoteKey = 0;
|
||||
|
||||
export function showFragmentQuote(text, color) {
|
||||
if (!text) return;
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
quoteKey++;
|
||||
fragmentQuote.set({ text, color, key: quoteKey });
|
||||
toastTimer = setTimeout(() => {
|
||||
fragmentQuote.set(null);
|
||||
toastTimer = null;
|
||||
}, 4200);
|
||||
}
|
||||
|
||||
// ── Level-complete overlay ────────────────────────────────────────────────────
|
||||
// Shows the color-psychology takeaway before the level-select redirect.
|
||||
|
||||
export const completeQuoteData = writable(null); // { text, color } | null
|
||||
|
||||
export function showCompleteQuote(text, color, nextDest) {
|
||||
if (!text) return;
|
||||
completeQuoteData.set({ text, color, nextDest });
|
||||
}
|
||||
|
||||
export function clearCompleteQuote() {
|
||||
completeQuoteData.set(null);
|
||||
}
|
||||