n. \<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dimensional Rift</title>
</head>
<body>
<div class="dimension-container">
<div class="dimension ps1-world" id="ps1World">
<div class="ps1-character"></div>
<div class="ps1-environment">
<div class="low-poly-object wall wall-left"></div>
<div class="low-poly-object wall wall-right"></div>
<div class="low-poly-object wall wall-back"></div>
<div class="low-poly-object floor"></div>
<div class="low-poly-object ceiling"></div>
</div>
<div class="ps1-fog"></div>
</div>
<div class="dimension vhs-world" id="vhsWorld">
<div class="vhs-static"></div>
<div class="vhs-overlay"></div>
<div class="vhs-tracking"></div>
<div class="vhs-content">
<div class="vhs-text">PLAY ME</div>
</div>
</div>
<div class="rift-tear" id="riftTear"></div>
<div class="interface">
<div class="instruction-overlay">
<div class="instruction-text">
<p class="instruction-title">HOW TO INTERACT WITH THE RIFT</p>
<p class="instruction-body">They say if you tear at reality long enough, something tears back.</p>
<p class="instruction-body">The entities have been waiting, trapped between frames of static and forgotten recordings.</p>
<p class="instruction-body">Drag to open tears in reality. Release to close them.</p>
<p class="instruction-body">Press SPACE or double-tap to banish what comes through.</p>
<p class="instruction-warning">What sees you through the rift remembers your face even after the tear is mended.</p>
</div>
</div>
<div class="glitch-text" data-text="DIMENSIONAL RIFT">DIMENSIONAL RIFT</div>
<div class="instructions">DRAG TO TEAR REALITY</div>
<div class="health-bar">
<div class="health-fill"></div>
</div>
<div class="glitch-meter">
<div class="glitch-fill"></div>
</div>
</div>
<div class="entities-container" id="entitiesContainer"></div>
<div class="noise-overlay"></div>
<div class="crt-lines"></div>
<div class="vignette"></div>
</div>
<audio id="ambient" loop>
<source src="https://assets.codepen.io/1948355/ambient-horror.mp3" type="audio/mpeg">
</audio>
<audio id="glitch-sound">
<source src="https://assets.codepen.io/1948355/glitch-sound.mp3" type="audio/mpeg">
</audio>
<audio id="entity-sound">
<source src="https://assets.codepen.io/1948355/entity-sound.mp3" type="audio/mpeg">
</audio>
</body>
</html>
@import url('https://fonts.googleapis.com/css2?family=VT323&family=Roboto+Mono:wght@400;700&display=swap');
:root {
--ps1-primary: #2a0e12;
--ps1-secondary: #3d1b1f;
--ps1-accent: #8b0000;
--vhs-primary: #000000;
--vhs-secondary: #111111;
--vhs-accent: #ff0066;
--glitch-color-1: #ff0000;
--glitch-color-2: #0000ff;
--glitch-color-3: #00ff00;
}
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: #000;
font-family: 'VT323', monospace;
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
color: #ffffff;
cursor: none;
}
.dimension-container {
position:center;
width: 100vw;
height: 100vh;
overflow: hidden;
perspective: 1000px;
}
.dimension {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: opacity 0.5s ease;
}
/* PS1 World Styling */
.ps1-world {
background-color: var(--ps1-primary);
perspective: 800px;
transform-style: preserve-3d;
z-index: 1;
}
.ps1-environment {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.low-poly-object {
position: absolute;
background-color: var(--ps1-secondary);
image-rendering: pixelated;
}
.wall {
width: 100%;
height: 100%;
background-image:
repeating-linear-gradient(
to right,
rgba(0, 0, 0, 0.1) 0px,
rgba(0, 0, 0, 0.1) 2px,
transparent 2px,
transparent 4px
),
repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0.1) 0px,
rgba(0, 0, 0, 0.1) 2px,
transparent 2px,
transparent 4px
);
}
.wall-left {
left: 0;
width: 1px;
transform: translateX(-50%) rotateY(90deg) translateZ(-50vw);
}
.wall-right {
right: 0;
width: 1px;
transform: translateX(50%) rotateY(-90deg) translateZ(-50vw);
}
.wall-back {
transform: translateZ(-100px);
}
.floor {
bottom: 0;
width: 100%;
height: 1px;
transform: translateY(50%) rotateX(90deg) translateZ(50vh);
background-image:
repeating-linear-gradient(
to right,
rgba(0, 0, 0, 0.2) 0px,
rgba(0, 0, 0, 0.2) 20px,
rgba(0, 0, 0, 0.1) 20px,
rgba(0, 0, 0, 0.1) 40px
),
repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0.2) 0px,
rgba(0, 0, 0, 0.2) 20px,
rgba(0, 0, 0, 0.1) 20px,
rgba(0, 0, 0, 0.1) 40px
);
}
.ceiling {
top: 0;
width: 100%;
height: 1px;
transform: translateY(-50%) rotateX(-90deg) translateZ(50vh);
background-color: var(--ps1-primary);
}
.ps1-character {
position: absolute;
width: 30px;
height: 60px;
bottom: 0;
left: 50%;
transform: translateX(-50%);
background-color: #333;
z-index: 2;
}
.ps1-fog {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(
circle at center,
transparent 0%,
rgba(0, 0, 0, 0.8) 80%
);
z-index: 3;
pointer-events: none;
}
/* VHS World Styling */
.vhs-world {
background-color: var(--vhs-primary);
z-index: 2;
opacity: 0;
}
.vhs-static {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.4'/%3E%3C/svg%3E");
opacity: 0.1;
z-index: 1;
}
.vhs-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
180deg,
rgba(255, 0, 102, 0.05) 0%,
rgba(0, 0, 255, 0.05) 100%
);
z-index: 2;
mix-blend-mode: screen;
}
.vhs-tracking {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 3;
pointer-events: none;
overflow: hidden;
}
.vhs-tracking::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 200%;
background: repeating-linear-gradient(
transparent 0%,
rgba(255, 255, 255, 0.05) 0.5%,
transparent 1%
);
animation: trackingLines 10s linear infinite;
}
.vhs-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 4;
text-align: center;
}
.vhs-text {
font-size: 5rem;
font-family: 'VT323', monospace;
color: var(--vhs-accent);
text-shadow: 4px 4px 0 rgba(0, 0, 0, 0.5);
animation: vhsFlicker 2s infinite;
}
/* Rift Tear Styling */
.rift-tear {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0;
height: 0;
background: linear-gradient(
135deg,
rgba(255, 0, 102, 0.7),
rgba(0, 0, 255, 0.7)
);
border-radius: 50%;
box-shadow: 0 0 20px rgba(255, 0, 102, 0.7),
0 0 40px rgba(0, 0, 255, 0.7);
z-index: 10;
transition: all 0.3s ease;
clip-path: polygon(
50% 0%,
60% 20%,
80% 10%,
70% 30%,
100% 40%,
80% 50%,
100% 60%,
80% 70%,
90% 90%,
70% 80%,
50% 100%,
30% 80%,
10% 90%,
20% 70%,
0% 60%,
20% 50%,
0% 40%,
30% 30%,
20% 10%,
40% 20%
);
}
/* Interface Styling */
.interface {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 20;
pointer-events: none;
padding: 20px;
}
.glitch-text {
font-size: 2rem;
position: relative;
text-transform: uppercase;
letter-spacing: 4px;
margin-bottom: 10px;
}
@media (prefers-reduced-motion: no-preference) {
.glitch-text::before,
.glitch-text::after {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.glitch-text::before {
left: 2px;
text-shadow: -2px 0 var(--glitch-color-1);
clip: rect(24px, 550px, 90px, 0);
animation: glitch-anim 3s infinite linear alternate-reverse;
}
.glitch-text::after {
left: -2px;
text-shadow: -2px 0 var(--glitch-color-2);
clip: rect(85px, 550px, 140px, 0);
animation: glitch-anim2 2.5s infinite linear alternate-reverse;
}
}
.instructions {
font-size: 1rem;
opacity: 0.7;
margin-bottom: 20px;
}
.health-bar, .glitch-meter {
width: 200px;
height: 15px;
background-color: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.3);
margin-bottom: 10px;
position: relative;
}
.health-fill {
height: 100%;
width: 100%;
background-color: #ff0000;
transition: width 0.3s ease;
}
.glitch-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #ff00ff, #00ffff);
transition: width 0.3s ease;
}
/* Entities Styling */
.entity {
position: absolute;
width: 40px;
height: 80px;
background-color: rgba(0, 0, 0, 0.8);
z-index: 5;
transition: transform 0.5s ease;
}
.entity::before {
content: '';
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 10px;
background-color: var(--vhs-accent);
box-shadow: 0 0 10px var(--vhs-accent);
}
/* Effects Overlays */
.noise-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.05'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 30;
mix-blend-mode: overlay;
}
.crt-lines {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0px,
rgba(0, 0, 0, 0) 1px,
rgba(0, 0, 0, 0.1) 1px,
rgba(0, 0, 0, 0.1) 2px
);
pointer-events: none;
z-index: 31;
}
.vignette {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(
ellipse at center,
rgba(0, 0, 0, 0) 60%,
rgba(0, 0, 0, 0.7) 100%
);
pointer-events: none;
z-index: 32;
}
/* Animations */
@keyframes trackingLines {
0% {
transform: translateY(-50%);
}
100% {
transform: translateY(0%);
}
}
@keyframes vhsFlicker {
0%, 100% {
opacity: 1;
}
92%, 94%, 96% {
opacity: 0.8;
}
93%, 95%, 97% {
opacity: 1;
}
}
@keyframes glitch-anim {
0% {
clip: rect(41px, 9999px, 44px, 0);
}
5% {
clip: rect(91px, 9999px, 100px, 0);
}
10% {
clip: rect(54px, 9999px, 99px, 0);
}
15% {
clip: rect(37px, 9999px, 56px, 0);
}
20% {
clip: rect(73px, 9999px, 75px, 0);
}
25% {
clip: rect(77px, 9999px, 100px, 0);
}
30% {
clip: rect(94px, 9999px, 98px, 0);
}
35% {
clip: rect(96px, 9999px, 99px, 0);
}
40% {
clip: rect(61px, 9999px, 97px, 0);
}
45% {
clip: rect(60px, 9999px, 73px, 0);
}
50% {
clip: rect(19px, 9999px, 33px, 0);
}
55% {
clip: rect(15px, 9999px, 37px, 0);
}
60% {
clip: rect(14px, 9999px, 49px, 0);
}
65% {
clip: rect(64px, 9999px, 94px, 0);
}
70% {
clip: rect(89px, 9999px, 100px, 0);
}
75% {
clip: rect(53px, 9999px, 54px, 0);
}
80% {
clip: rect(10px, 9999px, 54px, 0);
}
85% {
clip: rect(21px, 9999px, 59px, 0);
}
90% {
clip: rect(70px, 9999px, 74px, 0);
}
95% {
clip: rect(85px, 9999px, 87px, 0);
}
100% {
clip: rect(5px, 9999px, 53px, 0);
}
}
@keyframes glitch-anim2 {
0% {
clip: rect(65px, 9999px, 119px, 0);
}
5% {
clip: rect(133px, 9999px, 171px, 0);
}
10% {
clip: rect(13px, 9999px, 80px, 0);
}
15% {
clip: rect(137px, 9999px, 195px, 0);
}
20% {
clip: rect(24px, 9999px, 87px, 0);
}
25% {
clip: rect(132px, 9999px, 172px, 0);
}
30% {
clip: rect(24px, 9999px, 51px, 0);
}
35% {
clip: rect(54px, 9999px, 130px, 0);
}
40% {
clip: rect(123px, 9999px, 169px, 0);
}
45% {
clip: rect(123px, 9999px, 146px, 0);
}
50% {
clip: rect(132px, 9999px, 167px, 0);
}
55% {
clip: rect(19px, 9999px, 66px, 0);
}
60% {
clip: rect(94px, 9999px, 135px, 0);
}
65% {
clip: rect(69px, 9999px, 130px, 0);
}
70% {
clip: rect(96px, 9999px, 166px, 0);
}
75% {
clip: rect(134px, 9999px, 196px, 0);
}
80% {
clip: rect(18px, 9999px, 31px, 0);
}
85% {
clip: rect(61px, 9999px, 143px, 0);
}
90% {
clip: rect(13px, 9999px, 76px, 0);
}
95% {
clip: rect(6px, 9999px, 98px, 0);
}
100% {
clip: rect(25px, 9999px, 45px, 0);
}
}
/* Custom cursor */
.custom-cursor {
position: fixed;
width: 20px;
height: 20px;
border: 2px solid white;
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 9999;
mix-blend-mode: difference;
}
/* Reduced motion styles */
@media (prefers-reduced-motion: reduce) {
.glitch-text::before,
.glitch-text::after {
animation: none;
}
.vhs-tracking::before {
animation: none;
}
.vhs-text {
animation: none;
}
}
.instruction-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 40;
pointer-events: none;
opacity: 1;
transition: opacity 2s ease;
}
.instruction-text {
max-width: 80%;
padding: 20px;
background-color: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 0, 102, 0.3);
text-align: center;
}
.instruction-title {
font-size: 1.5rem;
margin-bottom: 15px;
color: var(--vhs-accent);
letter-spacing: 2px;
}
.instruction-body {
font-size: 1rem;
margin-bottom: 10px;
color: #ffffff;
opacity: 0.8;
}
.instruction-warning {
font-size: 0.9rem;
margin-top: 15px;
color: #ff0000;
font-style: italic;
}
document.addEventListener("DOMContentLoaded", () => {
// DOM Elements
const dimensionContainer = document.querySelector(".dimension-container");
const ps1World = document.getElementById("ps1World");
const vhsWorld = document.getElementById("vhsWorld");
const riftTear = document.getElementById("riftTear");
const entitiesContainer = document.getElementById("entitiesContainer");
const healthFill = document.querySelector(".health-fill");
const glitchFill = document.querySelector(".glitch-fill");
// Audio elements
const ambientSound = document.getElementById("ambient");
const glitchSound = document.getElementById("glitch-sound");
const entitySound = document.getElementById("entity-sound");
// Game state
let health = 100;
let glitchEnergy = 0;
let isDragging = false;
let mouseX = 0;
let mouseY = 0;
let riftSize = 0;
let worldBalance = 0; // 0 = PS1 world, 1 = VHS world
let entities = [];
let isReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)")
.matches;
// Create custom cursor
const cursor = document.createElement("div");
cursor.classList.add("custom-cursor");
document.body.appendChild(cursor);
// Initialize the experience
function init() {
// Set up event listeners
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mousedown", handleMouseDown);
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("touchstart", handleTouchStart, {
passive: false
});
document.addEventListener("touchmove", handleTouchMove, { passive: false });
document.addEventListener("touchend", handleTouchEnd);
setTimeout(() => {
const instructionOverlay = document.querySelector(".instruction-overlay");
if (instructionOverlay) {
instructionOverlay.style.opacity = "0";
// Remove from DOM after fade out
setTimeout(() => {
instructionOverlay.style.display = "none";
}, 3000);
}
}, 6000);
// Start ambient sound with low volume
if (ambientSound) {
ambientSound.volume = 0.4;
ambientSound.play().catch((e) => console.log("Audio couldn't play: ", e));
}
// Generate initial entities
generateEntities(3);
// Start game loop
requestAnimationFrame(gameLoop);
}
// Handle mouse movement
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
// Update custom cursor position
cursor.style.left = `${mouseX}px`;
cursor.style.top = `${mouseY}px`;
if (isDragging) {
updateRiftTear();
}
}
// Handle touch movement
function handleTouchMove(e) {
e.preventDefault();
if (e.touches.length > 0) {
mouseX = e.touches[0].clientX;
mouseY = e.touches[0].clientY;
if (isDragging) {
updateRiftTear();
}
}
}
// Handle mouse down
function handleMouseDown() {
isDragging = true;
cursor.style.transform = "translate(-50%, -50%) scale(0.8)";
// Position the rift tear at the cursor
riftTear.style.left = `${mouseX}px`;
riftTear.style.top = `${mouseY}px`;
}
// Handle touch start
function handleTouchStart(e) {
e.preventDefault();
isDragging = true;
if (e.touches.length > 0) {
mouseX = e.touches[0].clientX;
mouseY = e.touches[0].clientY;
// Position the rift tear at the touch point
riftTear.style.left = `${mouseX}px`;
riftTear.style.top = `${mouseY}px`;
}
}
// Handle mouse up
function handleMouseUp() {
isDragging = false;
cursor.style.transform = "translate(-50%, -50%) scale(1)";
// Shrink the rift tear
shrinkRiftTear();
}
// Handle touch end
function handleTouchEnd() {
isDragging = false;
// Shrink the rift tear
shrinkRiftTear();
}
// Update the rift tear
function updateRiftTear() {
// Increase rift size while dragging
riftSize = Math.min(riftSize + 2, 300);
// Update rift tear size
riftTear.style.width = `${riftSize}px`;
riftTear.style.height = `${riftSize}px`;
// Update world balance based on rift size
worldBalance = riftSize / 300;
// Update world visibility
vhsWorld.style.opacity = worldBalance;
// Increase glitch energy
glitchEnergy = Math.min(glitchEnergy + 0.5, 100);
glitchFill.style.width = `${glitchEnergy}%`;
// Create glitch effects
if (riftSize > 50 && !isReducedMotion) {
createGlitchEffect();
}
// Play glitch sound
if (glitchSound && riftSize % 30 === 0) {
glitchSound.currentTime = 0;
glitchSound.volume = 0.2;
glitchSound.play().catch((e) => {});
}
}
// Shrink the rift tear
function shrinkRiftTear() {
const shrinkInterval = setInterval(() => {
riftSize = Math.max(riftSize - 10, 0);
// Update rift tear size
riftTear.style.width = `${riftSize}px`;
riftTear.style.height = `${riftSize}px`;
// Update world balance based on rift size
worldBalance = riftSize / 300;
// Update world visibility
vhsWorld.style.opacity = worldBalance;
if (riftSize === 0) {
clearInterval(shrinkInterval);
}
}, 50);
}
// Create glitch effect
function createGlitchEffect() {
if (Math.random() > 0.7) {
const glitchType = Math.floor(Math.random() * 3);
switch (glitchType) {
case 0:
// Color shift
dimensionContainer.style.filter = `hue-rotate(${
Math.random() * 360
}deg)`;
setTimeout(() => {
dimensionContainer.style.filter = "";
}, 100);
break;
case 1:
// Displacement
dimensionContainer.style.transform = `translateX(${
Math.random() * 20 - 10
}px)`;
setTimeout(() => {
dimensionContainer.style.transform = "";
}, 100);
break;
case 2:
// Scanline glitch
const scanline = document.createElement("div");
scanline.classList.add("scanline-glitch");
scanline.style.cssText = `
position: absolute;
top: ${Math.random() * 100}%;
left: 0;
width: 100%;
height: ${Math.random() * 10 + 5}px;
background-color: rgba(255, 255, 255, 0.2);
z-index: 25;
`;
dimensionContainer.appendChild(scanline);
setTimeout(() => {
scanline.remove();
}, 200);
break;
}
}
}
// Generate entities
function generateEntities(count) {
for (let i = 0; i < count; i++) {
createEntity();
}
}
// Create entity
function createEntity() {
const entity = document.createElement("div");
entity.classList.add("entity");
// Random position outside the viewport
const side = Math.floor(Math.random() * 4);
let x, y;
switch (side) {
case 0: // Top
x = Math.random() * window.innerWidth;
y = -100;
break;
case 1: // Right
x = window.innerWidth + 100;
y = Math.random() * window.innerHeight;
break;
case 2: // Bottom
x = Math.random() * window.innerWidth;
y = window.innerHeight + 100;
break;
case 3: // Left
x = -100;
y = Math.random() * window.innerHeight;
break;
}
entity.style.left = `${x}px`;
entity.style.top = `${y}px`;
// Add entity to the container and track it
entitiesContainer.appendChild(entity);
entities.push({
element: entity,
x: x,
y: y,
speed: Math.random() * 0.5 + 0.5,
visible: worldBalance > 0.5 // Only visible in VHS world
});
return entity;
}
// Update entity positions
function updateEntities() {
entities.forEach((entity, index) => {
// Determine if entity should be visible based on world balance
entity.visible = worldBalance > 0.5;
entity.element.style.opacity = entity.visible ? "1" : "0";
if (entity.visible) {
// Move toward player
const dx = window.innerWidth / 2 - entity.x;
const dy = window.innerHeight / 2 - entity.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
const angle = Math.atan2(dy, dx);
entity.x += Math.cos(angle) * entity.speed;
entity.y += Math.sin(angle) * entity.speed;
entity.element.style.left = `${entity.x}px`;
entity.element.style.top = `${entity.y}px`;
// Apply glitch effect to entity
if (Math.random() > 0.95 && !isReducedMotion) {
entity.element.style.transform = `translateX(${
Math.random() * 10 - 5
}px)`;
setTimeout(() => {
entity.element.style.transform = "";
}, 100);
}
} else {
// Entity reached player, reduce health
damagePlayer(5);
// Remove entity
entity.element.remove();
entities.splice(index, 1);
// Play entity sound
if (entitySound) {
entitySound.currentTime = 0;
entitySound.volume = 0.4;
entitySound.play().catch((e) => {});
}
// Create new entity
setTimeout(() => {
createEntity();
}, 2000);
}
}
});
// Occasionally spawn new entities
if (Math.random() > 0.995 && entities.length < 5) {
createEntity();
}
}
// Damage player
function damagePlayer(amount) {
health = Math.max(health - amount, 0);
healthFill.style.width = `${health}%`;
// Screen shake effect
if (!isReducedMotion) {
dimensionContainer.style.animation = "shake 0.5s";
setTimeout(() => {
dimensionContainer.style.animation = "";
}, 500);
}
// Game over if health reaches 0
if (health <= 0) {
gameOver();
}
}
// Use glitch energy to attack entities
function useGlitchAttack() {
if (glitchEnergy >= 50) {
// Reduce glitch energy
glitchEnergy -= 50;
glitchFill.style.width = `${glitchEnergy}%`;
// Create attack effect
const attackEffect = document.createElement("div");
attackEffect.classList.add("glitch-attack");
attackEffect.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0;
height: 0;
background: radial-gradient(circle, rgba(255,0,255,0.5) 0%, rgba(0,255,255,0.5) 100%);
border-radius: 50%;
z-index: 20;
animation: expandAttack 1s forwards;
`;
dimensionContainer.appendChild(attackEffect);
// Add keyframes for attack animation
const style = document.createElement("style");
style.textContent = `
@keyframes expandAttack {
0% {
width: 0;
height: 0;
opacity: 1;
}
100% {
width: 200vw;
height: 200vw;
opacity: 0;
}
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
`;
document.head.appendChild(style);
// Remove entities
entities.forEach((entity) => {
entity.element.remove();
});
entities = [];
// Generate new entities after a delay
setTimeout(() => {
generateEntities(2);
}, 3000);
// Remove attack effect after animation
setTimeout(() => {
attackEffect.remove();
}, 1000);
}
}
// Game over
function gameOver() {
// Create game over overlay
const gameOverOverlay = document.createElement("div");
gameOverOverlay.classList.add("game-over");
gameOverOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
`;
const gameOverText = document.createElement("div");
gameOverText.textContent = "SIGNAL LOST";
gameOverText.style.cssText = `
font-size: 4rem;
color: #ff0000;
margin-bottom: 20px;
animation: glitchText 2s infinite;
`;
const restartButton = document.createElement("button");
restartButton.textContent = "RECONNECT";
restartButton.style.cssText = `
padding: 10px 20px;
background-color: transparent;
border: 2px solid #ff0000;
color: #ffffff;
font-family: 'VT323', monospace;
font-size: 1.5rem;
cursor: pointer;
`;
gameOverOverlay.appendChild(gameOverText);
gameOverOverlay.appendChild(restartButton);
dimensionContainer.appendChild(gameOverOverlay);
// Add restart functionality
restartButton.addEventListener("click", () => {
location.reload();
});
}
// Main game loop
function gameLoop() {
// Update entities
updateEntities();
// Request next frame
requestAnimationFrame(gameLoop);
}
// Add keyboard controls for glitch attack
document.addEventListener("keydown", (e) => {
if (e.code === "Space") {
useGlitchAttack();
}
});
// Add double tap for mobile glitch attack
let lastTap = 0;
document.addEventListener("touchend", (e) => {
const currentTime = new Date().getTime();
const tapLength = currentTime - lastTap;
if (tapLength < 300 && tapLength > 0) {
useGlitchAttack();
e.preventDefault();
}
lastTap = currentTime;
});
// Start the experience
init();
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.