1
0
Files
SignalElsewhereGame/index.html

555 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Signal Elsewhere</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #000;
overflow: hidden;
font-family: 'Courier New', monospace;
}
#matrixCanvas {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1;
opacity: 1;
transition: opacity 1s ease;
}
#textContainer {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
text-align: center;
font-size: 1.2rem;
line-height: 2;
visibility: hidden;
color: #00ff00;
}
.typewriter-line {
visibility: hidden;
white-space: nowrap;
margin: 0.5rem 0;
}
#followBtn {
position: fixed;
bottom: 5%;
left: 50%;
transform: translateX(-50%);
z-index: 3;
padding: 1rem 3rem;
font-size: 1.1rem;
background-color: #001100;
color: #00ff00;
border: 2px solid #00ff00;
cursor: pointer;
visibility: hidden;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease, background-color 0.2s ease;
font-family: 'Courier New', monospace;
letter-spacing: 2px;
}
#followBtn:hover {
background-color: #003300;
}
.scene {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 4;
background-color: #000;
color: #00ff00;
display: none;
justify-content: center;
align-items: center;
font-family: 'Courier New', monospace;
font-size: 1.2rem;
text-align: center;
padding: 2rem;
opacity: 0;
transition: opacity 1s ease;
}
#blinkCursor {
display: none;
width: 8px;
height: 14px;
background-color: #00ff00;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 1;
}
#scene2Text {
color: #00ff00;
font-family: 'Courier New', monospace;
font-size: 1.2rem;
line-height: 2;
text-align: center;
visibility: hidden;
}
#learnBtn {
position: fixed;
bottom: 5%;
left: 50%;
transform: translateX(-50%);
z-index: 5;
padding: 1rem 3rem;
font-size: 1.1rem;
background-color: #001100;
color: #00ff00;
border: 2px solid #00ff00;
cursor: pointer;
visibility: hidden;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease, background-color 0.2s ease;
font-family: 'Courier New', monospace;
letter-spacing: 2px;
}
#learnBtn:hover {
background-color: #003300;
}
</style>
</head>
<body>
<canvas id="matrixCanvas"></canvas>
<div id="textContainer">
<div class="typewriter-line" id="line1"></div>
<div class="typewriter-line" id="line2"></div>
<div class="typewriter-line" id="line3"></div>
</div>
<button id="followBtn">FOLLOW THEM</button>
<div id="scene2" class="scene">
<div id="scene2Text"></div>
<button id="learnBtn">LEARN</button>
<div id="blinkCursor"></div>
</div>
<script>
// Testing shortcuts state
let skipAnimations = false;
// Canvas setup for Matrix rain
const canvas = document.getElementById('matrixCanvas');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Matrix rain configuration
const fontSize = 14;
let columns = Math.floor(canvas.width / fontSize);
let drops = [];
const chars = '0123456789@#$%^&*()';
const rainDuration = 8000; // 8 seconds
let rainActive = false;
let rainStartTime = null;
// CRT flicker effect
function crtFlicker() {
let flickerCount = 0;
const maxFlickers = 3;
const flickerInterval = setInterval(() => {
ctx.fillStyle = flickerCount % 2 === 0 ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
flickerCount++;
if (flickerCount >= maxFlickers * 2) {
clearInterval(flickerInterval);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
startMatrixRain();
}
}, 100);
}
// Start Matrix rain animation
function startMatrixRain() {
rainActive = true;
rainStartTime = Date.now();
columns = Math.floor(canvas.width / fontSize);
drops = Array(columns).fill(0).map(() => Math.floor(Math.random() * canvas.height / fontSize));
drawMatrix();
}
// Draw Matrix rain frame
function drawMatrix() {
if (!rainActive) return;
// Create trail effect
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#00ff00';
ctx.font = `${fontSize}px 'Courier New', monospace`;
for (let i = 0; i < columns; i++) {
const x = i * fontSize;
const y = drops[i] * fontSize;
// Chance to render "484" as a horizontal string in this column
if (Math.random() < 0.003) {
ctx.fillText('484', x, y);
} else {
const text = chars[Math.floor(Math.random() * chars.length)];
ctx.fillText(text, x, y);
}
// Reset drop when it goes off screen
if (y > canvas.height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i] += 0.5;
}
// End rain after duration
if (Date.now() - rainStartTime > rainDuration) {
fadeRainToBackground();
rainActive = false;
} else {
requestAnimationFrame(drawMatrix);
}
}
// Fade rain to background
function fadeRainToBackground() {
const canvasElem = document.getElementById('matrixCanvas');
let opacity = 1;
const fadeInterval = setInterval(() => {
opacity -= 0.02;
if (opacity <= 0.15) {
opacity = 0.15;
clearInterval(fadeInterval);
document.getElementById('textContainer').style.visibility = 'visible';
typewriter();
}
canvasElem.style.opacity = opacity;
}, 16);
}
// Reusable typewriter function with custom delays
// If endCharDelay is provided, speed accelerates from charDelay to endCharDelay
function typewriterLines(lines, targetElem, onComplete, charDelay = 60, lineDelay = 500, endCharDelay = null) {
let currentLine = 0;
let currentChar = 0;
let totalChars = 0;
let charsTyped = 0;
// Calculate total characters if using accelerating speed
if (endCharDelay !== null) {
totalChars = lines.reduce((sum, line) => sum + line.length, 0);
}
function getDelay() {
if (endCharDelay === null || totalChars === 0) return charDelay;
// Linear interpolation from charDelay to endCharDelay
const progress = charsTyped / totalChars;
return charDelay + (endCharDelay - charDelay) * progress;
}
function tick() {
if (skipAnimations) {
// Instantly show all remaining text
let remainingText = '';
for (let i = currentLine; i < lines.length; i++) {
remainingText += lines[i];
}
targetElem.textContent = remainingText;
onComplete?.();
return;
}
if (currentLine >= lines.length) {
onComplete?.();
return;
}
targetElem.style.visibility = 'visible';
const lineText = lines[currentLine];
if (currentChar < lineText.length) {
targetElem.textContent += lineText[currentChar];
currentChar++;
charsTyped++;
setTimeout(tick, getDelay());
} else {
currentLine++;
currentChar = 0;
setTimeout(tick, lineDelay);
}
}
tick();
}
// Scene 1 typewriter
const scene1lines = [
"YOU ARE NOT IMAGINING THINGS",
"THE SYNCHRONICITIES YOU ARE EXPERIENCING HAVE ALWAYS BEEN THERE",
"THE ONLY THING THAT HAS CHANGED IS YOUR AWARENESS OF THEM"
];
let scene1LineIndex = 0;
let scene1Timeouts = [];
function typewriter() {
if (scene1LineIndex >= scene1lines.length) {
showButton();
return;
}
const lineElem = document.getElementById(`line${scene1LineIndex + 1}`);
lineElem.style.visibility = 'visible';
const lineText = scene1lines[scene1LineIndex];
let charIndex = 0;
function typeChar() {
if (charIndex < lineText.length) {
lineElem.textContent += lineText[charIndex];
charIndex++;
const t = setTimeout(typeChar, 60);
scene1Timeouts.push(t);
} else {
scene1LineIndex++;
const t = setTimeout(typewriter, 500);
scene1Timeouts.push(t);
}
}
typeChar();
}
function skipScene1() {
// Clear all pending timeouts
scene1Timeouts.forEach(t => clearTimeout(t));
scene1Timeouts = [];
// Show all text instantly
document.getElementById('line1').textContent = scene1lines[0];
document.getElementById('line2').textContent = scene1lines[1];
document.getElementById('line3').textContent = scene1lines[2];
document.getElementById('line1').style.visibility = 'visible';
document.getElementById('line2').style.visibility = 'visible';
document.getElementById('line3').style.visibility = 'visible';
showButton();
}
// Show "FOLLOW THEM" button
function showButton() {
const btn = document.getElementById('followBtn');
btn.style.visibility = 'visible';
let opacity = 0;
const fadeIn = setInterval(() => {
opacity += 0.05;
if (opacity >= 1) {
opacity = 1;
clearInterval(fadeIn);
btn.style.pointerEvents = 'auto';
}
btn.style.opacity = opacity;
}, 30);
}
// Scene transition functions
function transitionToScene2() {
const canvas = document.getElementById('matrixCanvas');
const textContainer = document.getElementById('textContainer');
const btn = document.getElementById('followBtn');
const scene2 = document.getElementById('scene2');
let opacity = 1;
const fadeOut = setInterval(() => {
opacity -= 0.05;
if (opacity <= 0) {
opacity = 0;
clearInterval(fadeOut);
canvas.style.display = 'none';
textContainer.style.display = 'none';
btn.style.display = 'none';
loadScene2(scene2);
}
canvas.style.opacity = opacity;
textContainer.style.opacity = opacity;
btn.style.opacity = opacity;
}, 30);
}
// Scene 2 loader with blinking cursor (5 blinks), and text
function loadScene2(sceneElem) {
sceneElem.style.display = 'flex';
const cursor = document.getElementById('blinkCursor');
const textContainer = document.getElementById('scene2Text');
const learnBtn = document.getElementById('learnBtn');
let opacity = 0;
const fadeIn = setInterval(() => {
opacity += 0.05;
if (opacity >= 1) {
opacity = 1;
clearInterval(fadeIn);
// Show blinking cursor - blink 5 times (5 seconds)
cursor.style.display = 'block';
cursor.style.opacity = '1';
let blinkCount = 0;
let isVisible = true;
const blinkInterval = setInterval(() => {
isVisible = !isVisible;
cursor.style.opacity = isVisible ? '1' : '0';
blinkCount++;
if (blinkCount >= 10) { // 5 complete cycles (on+off = 2 toggles per cycle)
clearInterval(blinkInterval);
cursor.style.display = 'none';
// Type the new pre-LEARN text - starts calm, accelerates to normal reading speed
typewriterLines(
[
"...AND THAT AWARENESS ISN'T JUST COINCIDENCE. YOU'RE SITTING IN FRONT OF THIS SCREEN AT THIS EXACT MOMENT IN TIME FOR A REASON.",
".....COULD THAT REASON BE THAT YOU NEED TO LEARN SOMETHING?"
],
textContainer,
() => {
// Show "LEARN" button
learnBtn.style.visibility = 'visible';
let btnOpacity = 0;
const btnFadeIn = setInterval(() => {
btnOpacity += 0.05;
if (btnOpacity >= 1) {
btnOpacity = 1;
clearInterval(btnFadeIn);
learnBtn.style.pointerEvents = 'auto';
}
learnBtn.style.opacity = btnOpacity;
}, 30);
},
120, // Start calm (120ms/char)
800, // Calm line delay
60 // Accelerate to normal reading speed (60ms/char)
);
}
}, 500); // 500ms on, 500ms off = 1 second per blink
}
sceneElem.style.opacity = opacity;
}, 30);
}
// "LEARN" button click handler
function handleLearnClick() {
const learnBtn = document.getElementById('learnBtn');
const textContainer = document.getElementById('scene2Text');
learnBtn.style.pointerEvents = 'none';
learnBtn.style.opacity = 0;
learnBtn.style.visibility = 'hidden';
// Clear previous text to prevent appending
textContainer.textContent = '';
typewriterLines(
[
"THE SIGNAL IS NOW RECEIVING.",
"PREPARE FOR THE NEXT TRANSMISSION."
],
textContainer,
() => {
// Placeholder for Scene 3
},
30, // Fast char delay for anxious rhythm
200 // Fast line delay for anxious rhythm
);
}
// Initialize on load
window.addEventListener('load', () => {
setTimeout(crtFlicker, 1500);
});
// Button click handlers
document.getElementById('followBtn').addEventListener('click', transitionToScene2);
document.getElementById('learnBtn').addEventListener('click', handleLearnClick);
// Keyboard shortcuts for testing
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
// Escape alone: Skip current animation to text
// Scene 1 skip (rain or typewriter)
const textContainer = document.getElementById('textContainer');
const canvas = document.getElementById('matrixCanvas');
if (textContainer.style.visibility !== 'visible') {
// Skip rain, show Scene 1 text instantly
canvas.style.opacity = '0.15';
skipScene1();
}
// Scene 2 pre-LEARN skip (cursor blinking)
const scene2Text = document.getElementById('scene2Text');
const learnBtn = document.getElementById('learnBtn');
const cursor = document.getElementById('blinkCursor');
if (cursor.style.display === 'block') {
// Skip cursor, show pre-LEARN text + LEARN button
cursor.style.display = 'none';
scene2Text.textContent = "...AND THAT AWARENESS ISN'T JUST COINCIDENCE. YOU'RE SITTING IN FRONT OF THIS SCREEN AT THIS EXACT MOMENT IN TIME FOR A REASON.";
scene2Text.style.visibility = 'visible';
learnBtn.style.visibility = 'visible';
learnBtn.style.opacity = '1';
learnBtn.style.pointerEvents = 'auto';
}
}
if (e.key === 'Backspace') {
e.preventDefault(); // Prevent browser back navigation
// Fast-forward to last scene (Scene 2 post-LEARN)
// Hide Scene 1 elements
document.getElementById('matrixCanvas').style.display = 'none';
document.getElementById('textContainer').style.display = 'none';
document.getElementById('followBtn').style.display = 'none';
// Show Scene 2 with post-LEARN text
const scene2 = document.getElementById('scene2');
const textContainer = document.getElementById('scene2Text');
const learnBtn = document.getElementById('learnBtn');
const cursor = document.getElementById('blinkCursor');
scene2.style.display = 'flex';
scene2.style.opacity = '1';
cursor.style.display = 'none';
learnBtn.style.display = 'none';
// Show post-LEARN text instantly
textContainer.textContent = "THE SIGNAL IS NOW RECEIVING. PREPARE FOR THE NEXT TRANSMISSION.";
textContainer.style.visibility = 'visible';
}
});
</script>
</body>
</html>