:root {
–ink: #f2efe8;
–dim: rgba(242, 239, 232, 0.55);
–faint: rgba(242, 239, 232, 0.12);
–glow: rgba(192, 218, 255, 0.7);
–bg: #050509;
}
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: var(–bg);
color: var(–ink);
font-family:
“Helvetica Neue”,
Helvetica,
Arial,
sans-serif;
}
body {
cursor: crosshair;
user-select: none;
}
canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
}
#sky {
z-index: 0;
}
#grain {
z-index: 6;
pointer-events: none;
opacity: 0.14;
mix-blend-mode: soft-light;
}
.vignette {
position: fixed;
inset: 0;
z-index: 4;
pointer-events: none;
background:
radial-gradient(
circle at center,
transparent 20%,
rgba(0, 0, 0, 0.08) 50%,
rgba(0, 0, 0, 0.76) 130%
);
}
.light {
position: fixed;
width: 55vw;
height: 55vw;
left: 50%;
top: 50%;
z-index: 1;
pointer-events: none;
border-radius: 50%;
transform: translate(-50%, -50%);
background:
radial-gradient(
circle,
rgba(171, 203, 255, 0.08),
rgba(121, 106, 255, 0.025) 35%,
transparent 70%
);
filter: blur(20px);
transition: left 1.8s cubic-bezier(.2,.8,.2,1),
top 1.8s cubic-bezier(.2,.8,.2,1);
}
main {
position: fixed;
inset: 0;
z-index: 3;
display: grid;
place-items: center;
pointer-events: none;
}
.object {
position: relative;
width: min(38vw, 460px);
aspect-ratio: 0.72;
pointer-events: auto;
transform-style: preserve-3d;
transition: transform 0.15s ease-out;
}
.door {
position: absolute;
inset: 0;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.17);
border-radius: 48% 48% 4px 4px / 23% 23% 4px 4px;
background:
radial-gradient(
circle at 50% 18%,
rgba(154, 185, 255, 0.1),
transparent 25%
),
linear-gradient(
180deg,
rgba(255, 255, 255, 0.035),
rgba(255, 255, 255, 0.004)
),
#08080e;
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.025),
0 0 80px rgba(77, 87, 145, 0.13),
inset 0 0 90px rgba(0, 0, 0, 0.85);
isolation: isolate;
}
.door::before {
content: “”;
position: absolute;
inset: 7%;
z-index: 2;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 48% 48% 2px 2px / 22% 22% 2px 2px;
pointer-events: none;
}
.door::after {
content: “”;
position: absolute;
inset: -30%;
z-index: 4;
pointer-events: none;
background:
conic-gradient(
from 0deg,
transparent,
rgba(255, 255, 255, 0.04),
transparent 20%,
transparent 75%,
rgba(151, 180, 255, 0.035),
transparent
);
animation: turn 24s linear infinite;
mix-blend-mode: screen;
}
@keyframes turn {
to {
transform: rotate(360deg);
}
}
.depth {
position: absolute;
inset: 0;
overflow: hidden;
border-radius: inherit;
background:
radial-gradient(
ellipse at center,
transparent 0%,
transparent 20%,
rgba(0, 0, 0, 0.35) 70%,
rgba(0, 0, 0, 0.95) 100%
);
}
.rings {
position: absolute;
inset: -50%;
transform: perspective(700px) rotateX(72deg);
transform-origin: center 61%;
}
.ring {
position: absolute;
left: 50%;
top: 54%;
width: var(–size);
height: var(–size);
border-radius: 50%;
border: 1px solid rgba(185, 205, 255, var(–alpha));
transform: translate(-50%, -50%);
filter: blur(var(–blur));
animation:
breathe var(–speed) ease-in-out infinite alternate,
drift calc(var(–speed) * 2.4) linear infinite;
}
@keyframes breathe {
from {
opacity: 0.15;
scale: 0.94;
}
to {
opacity: 0.9;
scale: 1.04;
}
}
@keyframes drift {
to {
rotate: 360deg;
}
}
.horizon {
position: absolute;
left: 0;
right: 0;
top: 58%;
height: 1px;
background: linear-gradient(
90deg,
transparent,
rgba(193, 211, 255, 0.45),
transparent
);
box-shadow: 0 0 18px rgba(170, 197, 255, 0.4);
opacity: 0.5;
animation: horizon 7s ease-in-out infinite;
}
@keyframes horizon {
0%,
100% {
opacity: 0.25;
transform: scaleX(0.75);
}
50% {
opacity: 0.85;
transform: scaleX(1);
}
}
.sun {
position: absolute;
width: 42%;
aspect-ratio: 1;
left: 50%;
top: 43%;
border-radius: 50%;
transform: translate(-50%, -50%);
background:
radial-gradient(
circle at 42% 38%,
#fff,
#d8e2ff 5%,
#9daee8 12%,
rgba(116, 120, 188, 0.28) 31%,
transparent 67%
);
filter: blur(0.3px);
opacity: 0.32;
animation: pulse 6s ease-in-out infinite;
mix-blend-mode: screen;
}
@keyframes pulse {
0%,
100% {
scale: 0.94;
opacity: 0.24;
filter: blur(1px);
}
50% {
scale: 1.04;
opacity: 0.5;
filter: blur(0);
}
}
.reflection {
position: absolute;
left: 50%;
top: 58%;
width: 45%;
height: 42%;
transform: translateX(-50%);
clip-path: polygon(45% 0, 55% 0, 100% 100%, 0 100%);
background:
repeating-linear-gradient(
180deg,
rgba(194, 211, 255, 0.17) 0 1px,
transparent 1px 8px
);
mask-image: linear-gradient(to bottom, white, transparent);
opacity: 0.36;
filter: blur(0.8px);
}
.message {
position: absolute;
left: 50%;
bottom: 12%;
z-index: 5;
width: 80%;
transform: translateX(-50%);
color: rgba(244, 245, 255, 0.82);
font-family: Georgia, “Times New Roman”, serif;
font-size: clamp(16px, 1.5vw, 23px);
font-weight: 400;
line-height: 1.45;
letter-spacing: 0.02em;
text-align: center;
text-wrap: balance;
text-shadow: 0 2px 24px #000;
transition:
opacity 0.8s ease,
transform 0.8s ease,
filter 0.8s ease;
}
.message.out {
opacity: 0;
transform: translate(-50%, 10px);
filter: blur(5px);
}
.edge-word {
position: fixed;
z-index: 5;
color: var(–dim);
font-size: 9px;
font-weight: 500;
letter-spacing: 0.28em;
text-transform: uppercase;
pointer-events: none;
transition: opacity 0.5s ease;
}
.north {
top: 24px;
left: 50%;
transform: translateX(-50%);
}
.south {
bottom: 22px;
left: 50%;
transform: translateX(-50%);
}
.west {
left: 21px;
top: 50%;
writing-mode: vertical-rl;
transform: translateY(-50%) rotate(180deg);
}
.east {
right: 21px;
top: 50%;
writing-mode: vertical-rl;
transform: translateY(-50%);
}
.counter {
position: fixed;
right: 24px;
bottom: 20px;
z-index: 5;
color: rgba(255, 255, 255, 0.28);
font-family: “Courier New”, monospace;
font-size: 10px;
letter-spacing: 0.13em;
pointer-events: none;
}
.coordinates {
position: fixed;
left: 24px;
bottom: 20px;
z-index: 5;
color: rgba(255, 255, 255, 0.28);
font-family: “Courier New”, monospace;
font-size: 10px;
letter-spacing: 0.13em;
pointer-events: none;
}
.whisper {
position: fixed;
z-index: 5;
color: rgba(255, 255, 255, 0.5);
font-family: Georgia, “Times New Roman”, serif;
font-size: 12px;
font-style: italic;
pointer-events: none;
opacity: 0;
transform: translate(-50%, -50%);
animation: whisper 4.5s ease forwards;
text-shadow: 0 2px 12px #000;
white-space: nowrap;
}
@keyframes whisper {
0% {
opacity: 0;
filter: blur(8px);
transform: translate(-50%, -30%);
}
18% {
opacity: 0.75;
filter: blur(0);
}
75% {
opacity: 0.55;
}
100% {
opacity: 0;
filter: blur(5px);
transform: translate(-50%, -75%);
}
}
.flash {
position: fixed;
inset: 0;
z-index: 10;
pointer-events: none;
opacity: 0;
background: #e8edff;
mix-blend-mode: screen;
}
.flash.go {
animation: flash 0.9s ease-out;
}
@keyframes flash {
0% {
opacity: 0;
}
8% {
opacity: 0.45;
}
100% {
opacity: 0;
}
}
.secret {
position: fixed;
inset: 0;
z-index: 12;
display: grid;
place-items: center;
pointer-events: none;
opacity: 0;
background: rgba(3, 3, 8, 0.82);
backdrop-filter: blur(12px);
transition: opacity 1.2s ease;
}
.secret.visible {
opacity: 1;
}
.secret p {
max-width: 680px;
padding: 40px;
color: rgba(255, 255, 255, 0.88);
font-family: Georgia, “Times New Roman”, serif;
font-size: clamp(24px, 4vw, 54px);
line-height: 1.12;
letter-spacing: -0.025em;
text-align: center;
text-wrap: balance;
}
@media (max-width: 700px) {
.object {
width: min(70vw, 390px);
}
.edge-word {
display: none;
}
.coordinates,
.counter {
bottom: 13px;
font-size: 8px;
}
}
The world did not end.
It became too large to see at once.
(() => {
const sky = document.getElementById(“sky”);
const ctx = sky.getContext(“2d”);
const grain = document.getElementById(“grain”);
const grainCtx = grain.getContext(“2d”);
const object = document.getElementById(“object”);
const door = document.getElementById(“door”);
const light = document.getElementById(“light”);
const message = document.getElementById(“message”);
const counter = document.getElementById(“counter”);
const coordinates = document.getElementById(“coordinates”);
const flash = document.getElementById(“flash”);
const secret = document.getElementById(“secret”);
const phrases = [
“You arrived before the place knew your name.”,
“Some doors remember every hand that never opened them.”,
“The horizon is only a line from this side.”,
“A future is watching you from very far away.”,
“There are versions of you that chose differently.”,
“Nothing vanished. It only moved beyond your language.”,
“The stars are not above us. They are around us.”,
“You have mistaken silence for absence.”,
“The universe keeps its oldest promises in the dark.”,
“Someone, somewhere, is standing here too.”,
“Every life contains a room it has not entered.”,
“You are closer than you were a moment ago.”
];
const whispers = [
“not yet”,
“look again”,
“it noticed”,
“closer”,
“behind you”,
“stay”,
“almost”,
“you remember”,
“listen”,
“again”,
“somewhere”,
“still here”
];
let width = 0;
let height = 0;
let dpr = Math.min(window.devicePixelRatio || 1, 2);
let mouseX = window.innerWidth / 2;
let mouseY = window.innerHeight / 2;
let smoothX = mouseX;
let smoothY = mouseY;
let stars = [];
let signals = [];
let signalCount = 0;
let phraseIndex = 0;
let doorClicks = 0;
let time = 0;
class Star {
constructor() {
this.reset(true);
}
reset(initial = false) {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.z = Math.random();
this.base = Math.random() * 0.9 + 0.1;
this.size = Math.random() * 1.3 + 0.15;
this.phase = Math.random() * Math.PI * 2;
this.speed = Math.random() * 0.008 + 0.002;
if (!initial) {
const edge = Math.floor(Math.random() * 4);
if (edge === 0) this.y = -10;
if (edge === 1) this.x = width + 10;
if (edge === 2) this.y = height + 10;
if (edge === 3) this.x = -10;
}
}
draw() {
this.phase += this.speed;
const parallaxX = (smoothX – width / 2) * this.z * -0.009;
const parallaxY = (smoothY – height / 2) * this.z * -0.009;
const alpha =
this.base *
(0.55 + Math.sin(this.phase) * 0.3) *
(0.35 + this.z * 0.65);
ctx.beginPath();
ctx.arc(
this.x + parallaxX,
this.y + parallaxY,
this.size * (0.4 + this.z),
0,
Math.PI * 2
);
ctx.fillStyle = `rgba(220, 228, 255, ${Math.max(0, alpha)})`;
ctx.fill();
if (this.size > 1.05 && alpha > 0.55) {
ctx.beginPath();
ctx.moveTo(
this.x + parallaxX – this.size * 3,
this.y + parallaxY
);
ctx.lineTo(
this.x + parallaxX + this.size * 3,
this.y + parallaxY
);
ctx.strokeStyle = `rgba(210, 222, 255, ${alpha * 0.16})`;
ctx.stroke();
}
}
}
class Signal {
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 1;
this.life = 1;
this.speed = 1.2 + Math.random() * 1.8;
this.points = [];
const count = 3 + Math.floor(Math.random() * 5);
for (let i = 0; i < count; i++) {
this.points.push({
angle: Math.random() * Math.PI * 2,
distance: 18 + Math.random() * 90,
size: 0.5 + Math.random() * 1.5
});
}
}
update() {
this.radius += this.speed;
this.life -= 0.007;
}
draw() {
const alpha = Math.max(0, this.life);
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(181, 205, 255, ${alpha * 0.28})`;
ctx.lineWidth = 1;
ctx.stroke();
ctx.beginPath();
ctx.arc(this.x, this.y, 2.2, 0, Math.PI * 2);
ctx.fillStyle = `rgba(240, 244, 255, ${alpha})`;
ctx.fill();
for (let i = 0; i new Star());
makeGrain();
}
function makeGrain() {
const image = grainCtx.createImageData(grain.width, grain.height);
const data = image.data;
for (let i = 0; i < data.length; i += 4) {
const value = Math.random() * 255;
data[i] = value;
data[i + 1] = value;
data[i + 2] = value;
data[i + 3] = Math.random() * 65;
}
grainCtx.putImageData(image, 0, 0);
}
function drawBackground() {
const gradient = ctx.createRadialGradient(
smoothX,
smoothY,
0,
width / 2,
height / 2,
Math.max(width, height)
);
gradient.addColorStop(0, "rgba(14, 15, 29, 1)");
gradient.addColorStop(0.35, "rgba(8, 8, 17, 1)");
gradient.addColorStop(1, "rgba(2, 2, 6, 1)");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
const haze = ctx.createRadialGradient(
width * 0.52,
height * 0.46,
0,
width * 0.52,
height * 0.46,
Math.min(width, height) * 0.55
);
haze.addColorStop(0, "rgba(66, 75, 126, 0.06)");
haze.addColorStop(0.5, "rgba(30, 34, 71, 0.025)");
haze.addColorStop(1, "transparent");
ctx.fillStyle = haze;
ctx.fillRect(0, 0, width, height);
}
function connectNearbyStars() {
const radius = 85;
for (let i = 0; i 180) continue;
for (let j = i + 1; j < Math.min(i + 20, stars.length); j++) {
const b = stars[j];
const dx = a.x – b.x;
const dy = a.y – b.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance signal.life > 0);
for (const signal of signals) {
signal.update();
signal.draw();
}
const nx = (smoothX / width – 0.5) * 2;
const ny = (smoothY / height – 0.5) * 2;
object.style.transform =
`perspective(900px) rotateY(${nx * 5}deg) rotateX(${-ny * 4}deg) ` +
`translate3d(${nx * 4}px, ${ny * 4}px, 0)`;
light.style.left = `${smoothX}px`;
light.style.top = `${smoothY}px`;
coordinates.textContent =
`${(smoothX / width * 180 – 90).toFixed(3)} / ` +
`${(smoothY / height * 360 – 180).toFixed(3)}`;
}
function changeMessage() {
message.classList.add(“out”);
setTimeout(() => {
phraseIndex = (phraseIndex + 1) % phrases.length;
message.textContent = phrases[phraseIndex];
message.classList.remove(“out”);
}, 650);
}
function createWhisper(x, y) {
if (Math.random() > 0.48) return;
const element = document.createElement(“div”);
element.className = “whisper”;
element.textContent =
whispers[Math.floor(Math.random() * whispers.length)];
element.style.left = `${x + (Math.random() – 0.5) * 160}px`;
element.style.top = `${y + (Math.random() – 0.5) * 100}px`;
document.body.appendChild(element);
setTimeout(() => element.remove(), 4600);
}
function createSignal(x, y) {
signals.push(new Signal(x, y));
signalCount++;
counter.textContent =
`SIGNALS: ${String(signalCount).padStart(3, “0”)}`;
createWhisper(x, y);
if (signalCount % 7 === 0) {
changeMessage();
}
if (signalCount === 13) {
flash.classList.remove(“go”);
void flash.offsetWidth;
flash.classList.add(“go”);
}
}
window.addEventListener(“resize”, resize);
window.addEventListener(“pointermove”, event => {
mouseX = event.clientX;
mouseY = event.clientY;
});
window.addEventListener(“pointerdown”, event => {
createSignal(event.clientX, event.clientY);
});
door.addEventListener(“click”, event => {
event.stopPropagation();
createSignal(event.clientX, event.clientY);
doorClicks++;
changeMessage();
door.animate(
[
{
transform: “scale(1)”,
filter: “brightness(1)”
},
{
transform: “scale(1.018)”,
filter: “brightness(1.3)”
},
{
transform: “scale(1)”,
filter: “brightness(1)”
}
],
{
duration: 900,
easing: “cubic-bezier(.2,.8,.2,1)”
}
);
if (doorClicks === 7) {
secret.classList.add(“visible”);
setTimeout(() => {
secret.classList.remove(“visible”);
doorClicks = 0;
}, 5000);
}
});
window.addEventListener(“keydown”, event => {
if (event.key === ” ” || event.key === “Enter”) {
createSignal(
width / 2 + (Math.random() – 0.5) * width * 0.4,
height / 2 + (Math.random() – 0.5) * height * 0.4
);
}
if (event.key.toLowerCase() === “r”) {
signalCount = 0;
doorClicks = 0;
signals = [];
phraseIndex = 0;
message.textContent = phrases[0];
counter.textContent = “SIGNALS: 000”;
}
});
setInterval(() => {
if (document.visibilityState === “visible”) {
makeGrain();
}
}, 180);
setInterval(() => {
if (document.visibilityState !== “visible”) return;
if (Math.random() > 0.65) return;
const x = Math.random() * width;
const y = Math.random() * height;
signals.push(new Signal(x, y));
}, 4200);
resize();
animate();
})();
