301 lines
12 KiB
TypeScript
301 lines
12 KiB
TypeScript
// SuperFun - stupid easter eggs to mess with your coworkers :D
|
|
|
|
declare global {
|
|
interface Window {
|
|
SuperFun?: () => void;
|
|
SuperFun2?: () => void;
|
|
SuperFun3?: () => void;
|
|
SuperFun4?: () => void;
|
|
SuperFunAll?: () => void;
|
|
}
|
|
}
|
|
|
|
interface SuperFunEgg {
|
|
isActive(): boolean;
|
|
enable(): void;
|
|
disable(): void;
|
|
}
|
|
|
|
// New eggs just push themselves here and SuperFunAll() picks them up for free.
|
|
const superFunEggs: SuperFunEgg[] = [];
|
|
|
|
function superFunToggle(egg: SuperFunEgg) {
|
|
if (egg.isActive()) egg.disable();
|
|
else egg.enable();
|
|
}
|
|
|
|
// --- SuperFun: Flip the screen and text
|
|
|
|
let superFunActive = false;
|
|
// Maps each touched text node back to its untouched content so we can restore it.
|
|
const superFunOriginals = new Map<Text, string>();
|
|
|
|
function superFunReverse(input: string): string {
|
|
return [...input].reverse().join("");
|
|
}
|
|
|
|
function superFunTextNodes(root: Node): Text[] {
|
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
acceptNode(node) {
|
|
if (!node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
|
|
const tag = node.parentElement?.tagName;
|
|
if (tag === "SCRIPT" || tag === "STYLE" || tag === "NOSCRIPT") return NodeFilter.FILTER_REJECT;
|
|
return NodeFilter.FILTER_ACCEPT;
|
|
},
|
|
});
|
|
|
|
const nodes: Text[] = [];
|
|
let node: Node | null;
|
|
while ((node = walker.nextNode())) nodes.push(node as Text);
|
|
return nodes;
|
|
}
|
|
|
|
// The flip lives on <body> while the 3D tilt (SuperFun2) lives on <html>, so the
|
|
// two transforms stack instead of clobbering each other when both are running.
|
|
const superFunFlip: SuperFunEgg = {
|
|
isActive: () => superFunActive,
|
|
enable() {
|
|
document.body.style.transform = "rotate(180deg)";
|
|
for (const node of superFunTextNodes(document.body)) {
|
|
superFunOriginals.set(node, node.nodeValue ?? "");
|
|
node.nodeValue = superFunReverse(node.nodeValue ?? "");
|
|
}
|
|
superFunActive = true;
|
|
},
|
|
disable() {
|
|
document.body.style.transform = "";
|
|
for (const [node, original] of superFunOriginals) node.nodeValue = original;
|
|
superFunOriginals.clear();
|
|
superFunActive = false;
|
|
},
|
|
};
|
|
superFunEggs.push(superFunFlip);
|
|
|
|
// --- SuperFun 2: Rotate viewport in 3d
|
|
|
|
let superFun2Active = false;
|
|
let superFun2Timer = 0;
|
|
const SUPER_FUN_2_INTERVAL = 1800;
|
|
const SUPER_FUN_2_MAX_TILT = 12; // degrees
|
|
|
|
function superFun2Angle(): number {
|
|
return (Math.random() * 2 - 1) * SUPER_FUN_2_MAX_TILT;
|
|
}
|
|
|
|
function superFun2Tumble() {
|
|
// perspective() inside the transform gives the page real depth as it tilts.
|
|
document.documentElement.style.transform =
|
|
`perspective(1200px) rotateX(${superFun2Angle().toFixed(2)}deg) ` +
|
|
`rotateY(${superFun2Angle().toFixed(2)}deg) rotateZ(${superFun2Angle().toFixed(2)}deg)`;
|
|
}
|
|
|
|
const superFun3d: SuperFunEgg = {
|
|
isActive: () => superFun2Active,
|
|
enable() {
|
|
document.documentElement.style.transition = `transform ${SUPER_FUN_2_INTERVAL}ms ease-in-out`;
|
|
superFun2Tumble();
|
|
superFun2Timer = window.setInterval(superFun2Tumble, SUPER_FUN_2_INTERVAL);
|
|
superFun2Active = true;
|
|
},
|
|
disable() {
|
|
window.clearInterval(superFun2Timer);
|
|
superFun2Timer = 0;
|
|
document.documentElement.style.transition = "";
|
|
document.documentElement.style.transform = "";
|
|
superFun2Active = false;
|
|
},
|
|
};
|
|
superFunEggs.push(superFun3d);
|
|
|
|
// --- SuperFun3: freecam - fly around a 3D version of the page ---
|
|
// Treats <body> as a flat plane floating in 3D and drives a virtual camera over
|
|
// it. Mouse (pointer-locked) looks, WASD moves relative to where you're facing,
|
|
// Space/Shift go up/down. The page transform each frame is the inverse of the
|
|
// camera's pose, which is what makes it feel like you're the one moving.
|
|
|
|
let superFun3Active = false;
|
|
let superFun3Raf = 0;
|
|
let superFun3Hud: HTMLDivElement | null = null;
|
|
const superFun3Keys: Record<string, boolean> = {};
|
|
const superFun3Cam = { x: 0, y: 0, z: 0, yaw: 0, pitch: 0 };
|
|
const SUPER_FUN_3_SPEED = 14; // px moved per frame while a key is held
|
|
const SUPER_FUN_3_SENS = 0.15; // degrees of look per px of mouse movement
|
|
const SUPER_FUN_3_PERSPECTIVE = 1200;
|
|
|
|
function superFun3IsMoveKey(code: string): boolean {
|
|
return code === "KeyW" || code === "KeyA" || code === "KeyS" || code === "KeyD"
|
|
|| code === "Space" || code === "ShiftLeft" || code === "ShiftRight";
|
|
}
|
|
|
|
function superFun3KeyDown(e: KeyboardEvent) {
|
|
// F toggles camera control: grab the pointer for look/move, or release it
|
|
// back to the cursor so you can actually click on the page.
|
|
if (e.code === "KeyF") {
|
|
e.preventDefault();
|
|
if (document.pointerLockElement == null) document.body.requestPointerLock();
|
|
else document.exitPointerLock();
|
|
return;
|
|
}
|
|
|
|
// Only hijack movement keys while we hold camera control; otherwise let the
|
|
// page handle them so the freed cursor can scroll/type/click normally.
|
|
if (document.pointerLockElement == null) return;
|
|
if (superFun3IsMoveKey(e.code)) e.preventDefault();
|
|
superFun3Keys[e.code] = true;
|
|
}
|
|
|
|
function superFun3KeyUp(e: KeyboardEvent) {
|
|
superFun3Keys[e.code] = false;
|
|
}
|
|
|
|
function superFun3MouseMove(e: MouseEvent) {
|
|
if (document.pointerLockElement == null) return;
|
|
superFun3Cam.yaw -= e.movementX * SUPER_FUN_3_SENS;
|
|
superFun3Cam.pitch += e.movementY * SUPER_FUN_3_SENS;
|
|
superFun3Cam.pitch = Math.max(-89, Math.min(89, superFun3Cam.pitch));
|
|
}
|
|
|
|
function superFun3UpdateHud() {
|
|
if (!superFun3Hud) return;
|
|
superFun3Hud.textContent = document.pointerLockElement != null
|
|
? "FREECAM - WASD move · mouse looks · Space / Shift up / down · F releases the cursor · SuperFun3() exits"
|
|
: "FREECAM - press F to grab the camera · cursor is free to click · SuperFun3() exits";
|
|
}
|
|
|
|
function superFun3LockChange() {
|
|
// Releasing control (via F or Esc) shouldn't leave movement keys stuck on.
|
|
if (document.pointerLockElement == null) {
|
|
for (const code in superFun3Keys) superFun3Keys[code] = false;
|
|
}
|
|
superFun3UpdateHud();
|
|
}
|
|
|
|
function superFun3Frame() {
|
|
// A rotation-only matrix gives us the camera's facing/right vectors (w=0 so
|
|
// the translation part is ignored) to move relative to where we're looking.
|
|
const rot = new DOMMatrix();
|
|
rot.rotateSelf(0, superFun3Cam.yaw, 0);
|
|
rot.rotateSelf(superFun3Cam.pitch, 0, 0);
|
|
const fwd = rot.transformPoint(new DOMPoint(0, 0, -1, 0));
|
|
const right = rot.transformPoint(new DOMPoint(1, 0, 0, 0));
|
|
|
|
const s = SUPER_FUN_3_SPEED;
|
|
if (superFun3Keys["KeyW"]) { superFun3Cam.x += fwd.x * s; superFun3Cam.y += fwd.y * s; superFun3Cam.z += fwd.z * s; }
|
|
if (superFun3Keys["KeyS"]) { superFun3Cam.x -= fwd.x * s; superFun3Cam.y -= fwd.y * s; superFun3Cam.z -= fwd.z * s; }
|
|
if (superFun3Keys["KeyD"]) { superFun3Cam.x += right.x * s; superFun3Cam.y += right.y * s; superFun3Cam.z += right.z * s; }
|
|
if (superFun3Keys["KeyA"]) { superFun3Cam.x -= right.x * s; superFun3Cam.y -= right.y * s; superFun3Cam.z -= right.z * s; }
|
|
if (superFun3Keys["Space"]) superFun3Cam.y -= s; // y grows downward in CSS, so up = subtract
|
|
if (superFun3Keys["ShiftLeft"] || superFun3Keys["ShiftRight"]) superFun3Cam.y += s;
|
|
|
|
// World transform = inverse of the camera pose T(cam) * Ry(yaw) * Rx(pitch).
|
|
const pose = new DOMMatrix();
|
|
pose.translateSelf(superFun3Cam.x, superFun3Cam.y, superFun3Cam.z);
|
|
pose.rotateSelf(0, superFun3Cam.yaw, 0);
|
|
pose.rotateSelf(superFun3Cam.pitch, 0, 0);
|
|
|
|
document.body.style.transform = `perspective(${SUPER_FUN_3_PERSPECTIVE}px) ${pose.inverse().toString()}`;
|
|
superFun3Raf = window.requestAnimationFrame(superFun3Frame);
|
|
}
|
|
|
|
function superFun3MakeHud(): HTMLDivElement {
|
|
const hud = document.createElement("div");
|
|
hud.textContent =
|
|
"FREECAM - press F to grab the camera · cursor is free to click · SuperFun3() exits";
|
|
hud.style.cssText = [
|
|
"position:fixed", "left:50%", "bottom:16px", "transform:translateX(-50%)",
|
|
"z-index:2147483647", "padding:8px 14px", "border-radius:8px",
|
|
"background:rgba(0,0,0,0.78)", "color:#fff", "font:13px/1.4 system-ui,sans-serif",
|
|
"pointer-events:none", "white-space:nowrap",
|
|
].join(";");
|
|
return hud;
|
|
}
|
|
|
|
const superFun3Freecam: SuperFunEgg = {
|
|
isActive: () => superFun3Active,
|
|
enable() {
|
|
superFun3Cam.x = 0; superFun3Cam.y = 0; superFun3Cam.z = 0;
|
|
superFun3Cam.yaw = 0; superFun3Cam.pitch = 0;
|
|
|
|
// Pivot the world around the current viewport center, and kill scrolling
|
|
// and per-frame transitions so the camera responds instantly.
|
|
document.body.style.transition = "none";
|
|
document.body.style.transformOrigin =
|
|
`${window.scrollX + window.innerWidth / 2}px ${window.scrollY + window.innerHeight / 2}px`;
|
|
document.documentElement.style.overflow = "hidden";
|
|
|
|
// The HUD hangs off <html> so the body's 3D transform doesn't fly it away.
|
|
superFun3Hud = superFun3MakeHud();
|
|
document.documentElement.appendChild(superFun3Hud);
|
|
|
|
window.addEventListener("keydown", superFun3KeyDown);
|
|
window.addEventListener("keyup", superFun3KeyUp);
|
|
window.addEventListener("mousemove", superFun3MouseMove);
|
|
document.addEventListener("pointerlockchange", superFun3LockChange);
|
|
|
|
superFun3Active = true;
|
|
superFun3Frame();
|
|
},
|
|
disable() {
|
|
window.cancelAnimationFrame(superFun3Raf);
|
|
superFun3Raf = 0;
|
|
|
|
window.removeEventListener("keydown", superFun3KeyDown);
|
|
window.removeEventListener("keyup", superFun3KeyUp);
|
|
window.removeEventListener("mousemove", superFun3MouseMove);
|
|
document.removeEventListener("pointerlockchange", superFun3LockChange);
|
|
for (const code in superFun3Keys) superFun3Keys[code] = false;
|
|
|
|
if (document.pointerLockElement != null) document.exitPointerLock();
|
|
superFun3Hud?.remove();
|
|
superFun3Hud = null;
|
|
|
|
document.body.style.transform = "";
|
|
document.body.style.transition = "";
|
|
document.body.style.transformOrigin = "";
|
|
document.documentElement.style.overflow = "";
|
|
superFun3Active = false;
|
|
},
|
|
};
|
|
superFunEggs.push(superFun3Freecam);
|
|
|
|
// --- SuperFun4: endlessly cycle the whole page through messed-up colors ---
|
|
// Uses an animated CSS filter on <html>. filter is its own property, so this
|
|
// layers on top of any of the transform-based eggs without fighting them.
|
|
|
|
let superFun4Active = false;
|
|
let superFun4Style: HTMLStyleElement | null = null;
|
|
|
|
const superFun4Egg: SuperFunEgg = {
|
|
isActive: () => superFun4Active,
|
|
enable() {
|
|
const style = document.createElement("style");
|
|
style.textContent = `@keyframes superfun4-colors {
|
|
0% { filter: hue-rotate(0deg) saturate(1.6); }
|
|
50% { filter: hue-rotate(180deg) saturate(2.4) invert(0.15); }
|
|
100% { filter: hue-rotate(360deg) saturate(1.6); }
|
|
}`;
|
|
document.head.appendChild(style);
|
|
superFun4Style = style;
|
|
document.documentElement.style.animation = "superfun4-colors 4s linear infinite";
|
|
superFun4Active = true;
|
|
},
|
|
disable() {
|
|
document.documentElement.style.animation = "";
|
|
superFun4Style?.remove();
|
|
superFun4Style = null;
|
|
superFun4Active = false;
|
|
},
|
|
};
|
|
superFunEggs.push(superFun4Egg);
|
|
|
|
// --- console entry points ---
|
|
|
|
window.SuperFun = () => superFunToggle(superFunFlip);
|
|
window.SuperFun2 = () => superFunToggle(superFun3d);
|
|
window.SuperFun3 = () => superFunToggle(superFun3Freecam);
|
|
window.SuperFun4 = () => superFunToggle(superFun4Egg);
|
|
|
|
// Marks this file as a module so the `declare global` Window augmentation above
|
|
// is honored. The bundler strips local exports, so nothing ships at runtime.
|
|
export {};
|