Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,11 @@ a:hover {
left: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
pointer-events: none; /* Allow clicks to pass through to the site if needed */
z-index: 9999; /* Ensures it sits ABOVE your website content */
pointer-events: none; /* Let's start with none so it doesn't block the heart */
}

/* Ensure the canvas itself fills the container */
#phaser-container canvas {
display: block;
}
353 changes: 272 additions & 81 deletions src/assets/js/eggs.js
Original file line number Diff line number Diff line change
@@ -1,113 +1,304 @@
/**
* eggs.js - The Space Invaders Easter Egg
* Logic: 5 Heart Clicks -> Emoji Explosion -> 80s Space Invaders
*/

// 1. GLOBAL CONSTANTS & STATE
const emojiBurst = [
"🎮",
"🕹️",
"👾",
"🚀",
"✨",
"⭐",
"🔥",
"💥",
"🌈",
"🎉",
"💖",
"💎",
"🤖",
"👻",
"🦄",
"🍄",
"🌍",
"⚡",
"🏆",
"🎯",
"🛸",
"👽",
"👾",
"🐙",
"🦖",
"🪐",
"🌌",
"🌠",
"☄️",
"🌙",
];

let heartClickCount = 0;
let phaserStarted = false;
let gameInstance;
let player;
let cursors;
let aliens;
let bullets;
let lastFired = 0;

// 2. DOM TRIGGER LOGIC (Integrate this with your footer heart)
const heart = document.getElementById("footer-heart");

if (heart) {
heart.style.cursor = "pointer";
heart.style.display = "inline-block"; // Necessary for transforms

heart.addEventListener("click", () => {
if (phaserStarted) return;

heartClickCount++;

// Visual feedback: Heart grows
const scaleAmount = 1 + heartClickCount * 0.3;
heart.style.transition =
"transform 0.1s cubic-bezier(0.17, 0.67, 0.83, 0.67)";
heart.style.transform = `scale(${scaleAmount})`;

if (heartClickCount === 5) {
phaserStarted = true;
heart.innerHTML = "🎮"; // Swap to gamer emoji
heart.style.transform = "scale(1.5)";

setTimeout(() => {
heart.style.opacity = "0"; // Fade out the heart
initPhaserGame();
}, 300);
}
});
}

// 3. PHASER ENGINE INITIALIZATION
function initPhaserGame() {
// Create dedicated Canvas
const canvas = document.createElement("canvas");
canvas.id = "phaser-game-canvas";
Object.assign(canvas.style, {
position: "fixed",
top: "0",
left: "0",
width: "100vw",
height: "100vh",
zIndex: "10000",
pointerEvents: "none", // Start as click-through
});
document.body.appendChild(canvas);

const config = {
type: Phaser.AUTO,
type: Phaser.CANVAS,
canvas: canvas,
width: window.innerWidth,
height: window.innerHeight,
parent: "phaser-container", // Make sure this div exists in your HTML
transparent: true,
physics: {
default: "arcade",
arcade: { gravity: { y: 300 } },
arcade: { gravity: { y: 0 }, debug: false },
},
scene: {
preload: preload,
create: create,
update: update,
},
};

const game = new Phaser.Game(config);
gameInstance = new Phaser.Game(config);
}

// 4. PHASER SCENE FUNCTIONS
function preload() {
// No need to preload images if we are only using text/emojis!
// No assets to load - we use emojis!
}

function create() {
const emojis = [
// Gaming & Tech
"🎮",
"🕹️",
"👾",
"🚀",
"💻",
"📱",
"⌨️",
"🖱️",
"🔋",
"🔌",
// Magic & Space
"✨",
"⭐",
"🌟",
"🔮",
"🌌",
"🌠",
"🌙",
"☄️",
"🛸",
"👽",
// Action & Fun
"🔥",
"💥",
"🧨",
"⚡",
"🌈",
"🎉",
"🎊",
"🎈",
"🎁",
"💎",
// Hearts & Expressions
"💖",
"🎯",
"🏆",
"🥇",
"🧿",
"🍀",
"🍕",
"🍭",
"🍦",
"🍩",
// Creatures & Icons
"🤖",
"👻",
"🐲",
"🦄",
"🦊",
"🐱",
"🐧",
"🦖",
"🍄",
"🌍",
];
const particles = spawnExplosion(this);

// After 5 seconds, clear explosion and start the real game
this.time.delayedCall(5000, () => {
this.tweens.add({
targets: particles.getChildren(),
alpha: 0,
duration: 1000,
onComplete: () => {
particles.clear(true, true);

// Make the game interactive
const canvas = document.getElementById("phaser-game-canvas");
if (canvas) canvas.style.pointerEvents = "auto";

setupSpaceInvaders.call(this);
},
});
});
}

function update() {
if (!player || !player.body) return;

// Movement
if (cursors.left.isDown) {
player.body.setVelocityX(-400);
} else if (cursors.right.isDown) {
player.body.setVelocityX(400);
} else {
player.body.setVelocityX(0);
}

// Shooting
if (cursors.space.isDown) {
fireBullet(this);
}
}

// 5. HELPER FUNCTIONS (The Mechanics)

function spawnExplosion(scene) {
const heartRect = document
.getElementById("footer-heart")
.getBoundingClientRect();
const particles = scene.add.group();

for (let i = 0; i < 75; i++) {
// 1. Pick a random emoji
const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];

// 2. Create the emoji at the heart's location
// We use this.add.text instead of this.physics.add.image
const particle = this.add.text(heartRect.left, heartRect.top, randomEmoji, {
for (let i = 0; i < 40; i++) {
const emoji = Phaser.Utils.Array.GetRandom(emojiBurst);
const p = scene.add.text(heartRect.left, heartRect.top, emoji, {
fontSize: "32px",
});

// 3. Manually add physics to the text object
this.physics.add.existing(particle);

// 4. Apply the "Explosion" physics
// Shoots them out in a cone shape upward
particle.body.setVelocity(
Phaser.Math.Between(-300, 300),
Phaser.Math.Between(-500, -1000),
scene.physics.add.existing(p);
p.body.setVelocity(
Phaser.Math.Between(-400, 400),
Phaser.Math.Between(-600, -1200),
);
p.body.setBounce(0.6);
p.body.setCollideWorldBounds(true);
p.body.setAngularVelocity(Phaser.Math.Between(-200, 200));

particles.add(p);
}
return particles;
}

function setupSpaceInvaders() {
const scene = this;

// Player Rocket
player = scene.add.text(
window.innerWidth / 2,
window.innerHeight - 80,
"🚀",
{ fontSize: "50px" },
);
scene.physics.add.existing(player);
player.body.setCollideWorldBounds(true);

// Bullets
bullets = scene.physics.add.group();

// Aliens Grid - Adjusted for smaller size
aliens = scene.physics.add.group();
const rows = 5;
const cols = 10;
const spacingX = 50; // Tighter horizontal spacing
const spacingY = 45; // Tighter vertical spacing

for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
let alienEmoji = ["👾", "👽", "🛸", "🐙", "👾"][y];
// Shrink from 35px to 24px
let alien = scene.add.text(
x * spacingX + 80,
y * spacingY + 80,
alienEmoji,
{ fontSize: "24px" },
);

scene.physics.add.existing(alien);
alien.body.setAllowGravity(false);
// Shrink the collision box to match the smaller emoji
alien.body.setSize(24, 24);

aliens.add(alien);
}
}

// Alien Movement Timer
scene.alienDirection = 1;
scene.time.addEvent({
delay: 800,
callback: moveAliens,
callbackScope: scene,
loop: true,
});

particle.body.setCollideWorldBounds(true);
particle.body.setBounce(0.7);
// Collisions
scene.physics.add.overlap(bullets, aliens, (bullet, alien) => {
bullet.destroy();
alien.destroy();
if (aliens.countActive() === 0) {
alert("INVADERS REPELLED! YOU WIN!");
window.location.reload();
}
});

// Optional: Add a little random rotation for flair
particle.setAngle(Phaser.Math.Between(0, 360));
cursors = scene.input.keyboard.createCursorKeys();
}

function moveAliens() {
let hitEdge = false;
const padding = 60;
const children = aliens.getChildren();

children.forEach((alien) => {
if (this.alienDirection === 1 && alien.x > window.innerWidth - padding)
hitEdge = true;
if (this.alienDirection === -1 && alien.x < padding) hitEdge = true;
});

if (hitEdge) {
this.alienDirection *= -1;
children.forEach((alien) => {
alien.y += 40;
alien.x += this.alienDirection * 10;
});
} else {
children.forEach((alien) => {
alien.x += 25 * this.alienDirection;
});
}
}
function fireBullet(scene) {
const now = scene.time.now;
if (now - lastFired < 400) return;

// 1. Create the bullet slightly above the player's center
const bullet = scene.add.text(
player.x + player.width / 2 - 10,
player.y - 20,
"🔥",
{
fontSize: "20px",
},
);

// 2. Add to physics and the group
scene.physics.add.existing(bullet);
bullets.add(bullet);

// 3. FAIL-SAFES
bullet.body.setAllowGravity(false); // Ensure gravity isn't pulling it down
bullet.body.setImmovable(false); // Ensure it's allowed to move
bullet.body.setVelocityY(-600); // Set the upward speed

// 4. Force a sync between the physics body and the Text object
bullet.body.isCircle = true; // Often helps with collision detection for small objects

lastFired = now;
}
Loading