cleaner presentation and adding a second experience

This commit is contained in:
2026-05-10 03:22:47 +09:00
parent 6a2ed1cbe4
commit 1b55e33ba3
5 changed files with 215 additions and 66 deletions

View File

@@ -1,15 +1,9 @@
html,
body {
position: relative;
width: 100%;
height: 100%;
}
body {
color: #333;
margin: 0;
padding: 8px;
padding: 0;
width: 100%;
min-height: 100%;
background: #000;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
}

View File

@@ -1,23 +1,117 @@
<script>
import { onMount } from 'svelte';
import Router from 'svelte-spa-router';
// importing all the different screens
import Home from './routes/Home.svelte';
import Home from './routes/Home.svelte';
import LevelSelect from './routes/LevelSelect.svelte';
import Game from './routes/Game.svelte';
import Game from './routes/Game.svelte';
import GameOver from './routes/GameOver.svelte';
import Win from './routes/Win.svelte';
import Win from './routes/Win.svelte';
const routes = {
'/': Home,
'/': Home,
'/levelselect': LevelSelect,
'/game': Game,
'/gameover': GameOver,
'/win': Win,
'/game': Game,
'/gameover': GameOver,
'/win': Win,
};
const COLORS = [
'#970505', '#CF8917', '#E3D214', '#39BD1C',
'#12B6C8', '#170CB7', '#6613BA', '#C71287',
'#753F16', '#FFD700',
];
let stars = [];
onMount(() => {
stars = Array.from({ length: 90 }, () => ({
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 2 + 1,
color: COLORS[Math.floor(Math.random() * COLORS.length)],
delay: -(Math.random() * 6), // negative = start mid-animation so they don't all blink at once
duration: Math.random() * 3 + 2,
}));
});
</script>
<Router {routes} />
<div class="page">
<!-- star background -->
<div class="starfield" aria-hidden="true">
{#each stars as s}
<span
class="star"
style="
left: {s.x}%;
top: {s.y}%;
width: {s.size}px;
height: {s.size}px;
background: {s.color};
box-shadow: 0 0 {s.size * 2}px {s.color};
animation-duration: {s.duration}s;
animation-delay: {s.delay}s;
"
></span>
{/each}
</div>
<!-- content column -->
<div class="stage">
<p class="game-title">the full hue</p>
<Router {routes} />
</div>
</div>
<style>
.page {
min-height: 100vh;
background: #000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
/* ── starfield ── */
.starfield {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
}
.star {
position: absolute;
border-radius: 50%;
animation: blink linear infinite;
}
@keyframes blink {
0%,100% { opacity: 0.9; }
50% { opacity: 0.08; }
}
/* ── content ── */
.stage {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.game-title {
margin: 0;
font-family: 'Courier New', Courier, monospace;
font-size: 15px;
letter-spacing: 0.35em;
text-transform: lowercase;
color: rgba(255, 255, 255, 0.28);
}
</style>

View File

@@ -134,18 +134,19 @@
fragments.push(new Fragment(p, fragData.x, fragData.y, fragData.color));
}
// create enemies from level data
// create enemies and tar only when the game hasn't been completed —
// after beating all levels the player can explore freely
enemies = [];
for (const enemyData of levelData.enemies) {
const e = new Enemy(p, enemyData.x, enemyData.y);
e.patrolDistance = enemyData.patrol;
enemies.push(e);
}
// create tar puddles from level data
tarPuddles = [];
for (const tarData of levelData.tar) {
tarPuddles.push(new TarPuddle(p, tarData.x, tarData.y));
if (!get(gameCompleted)) {
for (const enemyData of levelData.enemies) {
const e = new Enemy(p, enemyData.x, enemyData.y);
e.patrolDistance = enemyData.patrol;
enemies.push(e);
}
for (const tarData of levelData.tar) {
tarPuddles.push(new TarPuddle(p, tarData.x, tarData.y));
}
}
};
@@ -228,12 +229,15 @@
}
// ── 8. FELL OFF SCREEN ────────────────────────────────────────
// if player falls below the canvas, lose a life and respawn
if (player.sprite.y > p.height + 100 && gameState === 'playing') {
const dead = player.loseLife(lives);
if (dead) {
gameState = 'gameover';
setTimeout(() => push(`/gameover?level=${levelNumber}`), 500);
if (get(gameCompleted)) {
player.respawn(); // NG+: no damage, just put them back
} else {
const dead = player.loseLife(lives);
if (dead) {
gameState = 'gameover';
setTimeout(() => push(`/gameover?level=${levelNumber}`), 500);
}
}
}

View File

@@ -9,7 +9,7 @@ export class Enemy{
this.speed = 1.5;
this.direction = 1 // 1 is right, -1 is left
this.startX = x;
this.patrolDisctance = 100; // how far it can walk
this.patrolDistance = 100; // how far it can walk
}
update(){

View File

@@ -1,6 +1,6 @@
<script>
import {push} from 'svelte-spa-router';
import {unlockedColors} from '../stores/colorStore.js';
import { push } from 'svelte-spa-router';
import { gameCompleted } from '../stores/colorStore.js';
function startGame() {
push('/levelselect');
@@ -8,32 +8,44 @@
</script>
<div class="title-screen">
<!--<img src="/backgrounds/title_bg.png" class="bg" alt="title background"/>-->
<div class="content">
<h1>The Full Hue</h1>
<p>Bring back your color</p>
<button on:click={startGame}>begin</button>
</div>
{#if $gameCompleted}
<!-- ── NG+ home screen ── -->
<div class="content">
<h1 class="rainbow-title">The Full Hue</h1>
<p class="ng-lead">you've brought all the color back.</p>
<p class="ng-body">
go through the world again — now in full color and without sorrows.<br>
take your time. view the scenery. learn about your hues.
</p>
<button on:click={startGame}>go again</button>
</div>
{:else}
<!-- ── original home screen ── -->
<div class="content">
<h1>The Full Hue</h1>
<p class="tagline">Bring back your color</p>
<button on:click={startGame}>begin</button>
</div>
{/if}
</div>
<style>
.title-screen{
.title-screen {
width: 800px;
height: 450;
height: 450px;
margin: 0 auto;
position: relative;
overflow: hidden;
background: #111;
height: 450px;
}
.bg{
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.85;
}
.content{
.content {
position: absolute;
inset: 0;
display: flex;
@@ -42,31 +54,76 @@
justify-content: center;
gap: 12px;
color: white;
padding: 0 80px;
}
h1{
font-family:'Courier New', Courier, monospace;
/* ── shared heading ── */
h1 {
font-family: 'Courier New', Courier, monospace;
font-size: 64px;
font-weight: 400;
text-shadow: 0 2px 12px rgba(0,0,0,0.7);
margin: 0;
text-shadow: 0 2px 12px rgba(0,0,0,0.7);
}
p{
font-family:'Courier New', Courier, monospace;
/* ── original tagline ── */
.tagline {
font-family: 'Courier New', Courier, monospace;
font-size: 22px;
opacity: 0.8;
margin: 0;
}
button{
/* ── NG+ rainbow title ── */
.rainbow-title {
background: linear-gradient(
90deg,
#FF4136, #FF851B, #FFDC00, #2ECC40,
#0074D9, #B10DC9, #FF69B4, #FF4136
);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: shimmer 4s linear infinite;
}
@keyframes shimmer {
from { background-position: 0% center; }
to { background-position: 200% center; }
}
/* ── NG+ text ── */
.ng-lead {
font-family: 'Courier New', Courier, monospace;
font-size: 18px;
color: rgba(255, 255, 255, 0.75);
margin: 4px 0 0;
text-align: center;
}
.ng-body {
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
color: rgba(255, 255, 255, 0.45);
line-height: 1.75;
margin: 0;
text-align: center;
}
/* ── shared button ── */
button {
margin-top: 20px;
padding: 12px 40px;
font-family:'Courier New', Courier, monospace;
font-family: 'Courier New', Courier, monospace;
font-size: 22px;
background: rgba(255,255,255,0.15);
background: rgba(255, 255, 255, 0.15);
color: white;
border: 1px solid rgba(255,255,255,0.4);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 30px;
cursor: pointer;
transition: background 0.2s;
}
button:hover{background: rgba(255,255,255,0.3);}
button:hover { background: rgba(255, 255, 255, 0.3); }
</style>