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, html,
body { body {
position: relative;
width: 100%;
height: 100%;
}
body {
color: #333;
margin: 0; margin: 0;
padding: 8px; padding: 0;
width: 100%;
min-height: 100%;
background: #000;
box-sizing: border-box; box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
} }

View File

@@ -1,7 +1,7 @@
<script> <script>
import { onMount } from 'svelte';
import Router from 'svelte-spa-router'; 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 LevelSelect from './routes/LevelSelect.svelte';
import Game from './routes/Game.svelte'; import Game from './routes/Game.svelte';
@@ -15,9 +15,103 @@ const routes = {
'/gameover': GameOver, '/gameover': GameOver,
'/win': Win, '/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> </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,19 +134,20 @@
fragments.push(new Fragment(p, fragData.x, fragData.y, fragData.color)); 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 = []; enemies = [];
tarPuddles = [];
if (!get(gameCompleted)) {
for (const enemyData of levelData.enemies) { for (const enemyData of levelData.enemies) {
const e = new Enemy(p, enemyData.x, enemyData.y); const e = new Enemy(p, enemyData.x, enemyData.y);
e.patrolDistance = enemyData.patrol; e.patrolDistance = enemyData.patrol;
enemies.push(e); enemies.push(e);
} }
// create tar puddles from level data
tarPuddles = [];
for (const tarData of levelData.tar) { for (const tarData of levelData.tar) {
tarPuddles.push(new TarPuddle(p, tarData.x, tarData.y)); tarPuddles.push(new TarPuddle(p, tarData.x, tarData.y));
} }
}
}; };
p.draw = () => { p.draw = () => {
@@ -228,14 +229,17 @@
} }
// ── 8. FELL OFF SCREEN ──────────────────────────────────────── // ── 8. FELL OFF SCREEN ────────────────────────────────────────
// if player falls below the canvas, lose a life and respawn
if (player.sprite.y > p.height + 100 && gameState === 'playing') { if (player.sprite.y > p.height + 100 && gameState === 'playing') {
if (get(gameCompleted)) {
player.respawn(); // NG+: no damage, just put them back
} else {
const dead = player.loseLife(lives); const dead = player.loseLife(lives);
if (dead) { if (dead) {
gameState = 'gameover'; gameState = 'gameover';
setTimeout(() => push(`/gameover?level=${levelNumber}`), 500); setTimeout(() => push(`/gameover?level=${levelNumber}`), 500);
} }
} }
}
// ── 9. DRAW SPRITES LAST ────────────────────────────────────── // ── 9. DRAW SPRITES LAST ──────────────────────────────────────
// p5play renders all sprites after everything else is drawn // p5play renders all sprites after everything else is drawn

View File

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

View File

@@ -1,6 +1,6 @@
<script> <script>
import {push} from 'svelte-spa-router'; import { push } from 'svelte-spa-router';
import {unlockedColors} from '../stores/colorStore.js'; import { gameCompleted } from '../stores/colorStore.js';
function startGame() { function startGame() {
push('/levelselect'); push('/levelselect');
@@ -8,32 +8,44 @@
</script> </script>
<div class="title-screen"> <div class="title-screen">
<!--<img src="/backgrounds/title_bg.png" class="bg" alt="title background"/>-->
{#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"> <div class="content">
<h1>The Full Hue</h1> <h1>The Full Hue</h1>
<p>Bring back your color</p> <p class="tagline">Bring back your color</p>
<button on:click={startGame}>begin</button> <button on:click={startGame}>begin</button>
</div> </div>
{/if}
</div> </div>
<style> <style>
.title-screen{ .title-screen {
width: 800px; width: 800px;
height: 450; height: 450px;
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
background: #111; background: #111;
height: 450px;
} }
.bg{
width: 100%; .content {
height: 100%;
object-fit: cover;
opacity: 0.85;
}
.content{
position: absolute; position: absolute;
inset: 0; inset: 0;
display: flex; display: flex;
@@ -42,31 +54,76 @@
justify-content: center; justify-content: center;
gap: 12px; gap: 12px;
color: white; 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-size: 64px;
font-weight: 400; font-weight: 400;
text-shadow: 0 2px 12px rgba(0,0,0,0.7);
margin: 0; 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; font-size: 22px;
opacity: 0.8; opacity: 0.8;
margin: 0; 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; margin-top: 20px;
padding: 12px 40px; padding: 12px 40px;
font-family:'Courier New', Courier, monospace; font-family: 'Courier New', Courier, monospace;
font-size: 22px; font-size: 22px;
background: rgba(255,255,255,0.15); background: rgba(255, 255, 255, 0.15);
color: white; color: white;
border: 1px solid rgba(255,255,255,0.4); border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 30px; border-radius: 30px;
cursor: pointer; cursor: pointer;
transition: background 0.2s; transition: background 0.2s;
} }
button:hover{background: rgba(255,255,255,0.3);}
button:hover { background: rgba(255, 255, 255, 0.3); }
</style> </style>