cleaner presentation and adding a second experience
This commit is contained in:
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
<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} />
|
<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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(){
|
||||||
|
|||||||
@@ -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,31 +8,43 @@
|
|||||||
</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%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -42,20 +54,64 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
color: white;
|
color: white;
|
||||||
|
padding: 0 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── shared heading ── */
|
||||||
h1 {
|
h1 {
|
||||||
font-family: 'Courier New', Courier, monospace;
|
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{
|
|
||||||
|
/* ── original tagline ── */
|
||||||
|
.tagline {
|
||||||
font-family: 'Courier New', Courier, monospace;
|
font-family: 'Courier New', Courier, monospace;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 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 {
|
button {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 12px 40px;
|
padding: 12px 40px;
|
||||||
@@ -68,5 +124,6 @@
|
|||||||
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>
|
||||||
Reference in New Issue
Block a user