new and improved easter eggs (with sound effects!)

This commit is contained in:
IZZY 2025-06-01 15:46:17 +01:00
parent 74ed7bd50a
commit daa50887d6
4 changed files with 298 additions and 169 deletions

BIN
public/earthquake.mp3 Normal file

Binary file not shown.

View File

@ -248,7 +248,7 @@ const ContactUs = () => {
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
> >
<span className="sr-only">Instagram</span> <span className="sr-only">Instagram</span>
<Image height={200} width={200} alt="Logo" className="z-10" src="/insta.webp" /> <Image height={200} width={200} alt="Logo" className="z-10" src="/instagram.png" />
</a> </a>
{/* Facebook: Pulsating Map */} {/* Facebook: Pulsating Map */}
<a <a

View File

@ -3,8 +3,8 @@
@tailwind utilities; @tailwind utilities;
:root { :root {
--background: #ffffff; --background: #ffffff;
--foreground: #171717; --foreground: #171717;
} }
/* @media (prefers-color-scheme: dark) { /* @media (prefers-color-scheme: dark) {
@ -15,216 +15,319 @@
} */ } */
body { body {
color: var(--foreground); color: var(--foreground);
background: var(--background); background: var(--background);
} }
/* Increase specificity and use !important where necessary */ /* Increase specificity and use !important where necessary */
.mapboxgl-popup .mapboxgl-popup-content { .mapboxgl-popup .mapboxgl-popup-content {
@apply rounded-xl p-4 px-5 drop-shadow-lg border border-neutral-300 max-w-xs !important; @apply rounded-xl p-4 px-5 drop-shadow-lg border border-neutral-300 max-w-xs !important;
} }
/* Hide the popup tip */ /* Hide the popup tip */
.mapboxgl-popup .mapboxgl-popup-tip { .mapboxgl-popup .mapboxgl-popup-tip {
display: none !important; display: none !important;
} }
/* Child elements */ /* Child elements */
.mapboxgl-popup-content h3 { .mapboxgl-popup-content h3 {
@apply text-sm font-medium text-neutral-800 !important; @apply text-sm font-medium text-neutral-800 !important;
} }
.mapboxgl-popup-content p { .mapboxgl-popup-content p {
@apply text-xs text-neutral-600 !important; @apply text-xs text-neutral-600 !important;
} }
.mapboxgl-popup-content p + p { .mapboxgl-popup-content p+p {
@apply text-neutral-500 !important; @apply text-neutral-500 !important;
} }
.icon-link { .icon-link {
/* default styles if needed */ /* default styles if needed */
} }
.icon-link:hover, .icon-link:hover,
.icon-link:focus { .icon-link:focus {
background-color: #16424b; background-color: #16424b;
} }
.icon-link:hover h3, .icon-link:hover h3,
.icon-link:focus h3, .icon-link:focus h3,
.icon-link:hover p, .icon-link:hover p,
.icon-link:focus p { .icon-link:focus p {
color: #fff !important; color: #fff !important;
} }
.icon-link:hover h3, .icon-link:hover h3,
.icon-link:hover p, .icon-link:hover p,
.icon-link:focus h3, .icon-link:focus h3,
.icon-link:focus p { .icon-link:focus p {
color: #111; color: #111;
/* or black */ /* or black */
} }
/* ---- LAVA FLOOD OVERLAY ---- */ /* ---- LAVA FLOOD OVERLAY ---- */
.lava-flood-overlay { .lava-flood-overlay {
pointer-events: none; pointer-events: none;
position: fixed; position: fixed;
top: -100vh; top: -100vh;
left: 0; left: 0;
right: 0; right: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
z-index: 9999; z-index: 9999;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
transition: top 0.9s cubic-bezier(0.6, 0, 0.2, 1); transition: top 0.9s cubic-bezier(0.6, 0, 0.2, 1);
} }
.lava-flood-overlay.lava-active { .lava-flood-overlay.lava-active {
top: 0; top: 0;
transition: top 0.33s cubic-bezier(0.6, 0, 0.2, 1); transition: top 0.33s cubic-bezier(0.6, 0, 0.2, 1);
} }
.lava-flood-overlay img, .lava-flood-overlay img,
.lava-gradient { .lava-gradient {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
object-fit: cover; object-fit: cover;
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
filter: brightness(1.15) saturate(1.8) drop-shadow(0 0 80px #ff5500); filter: brightness(1.15) saturate(1.8) drop-shadow(0 0 80px #ff5500);
} }
/* ---- INSANE SCREEN SHAKE (LinkedIn) ---- */ /* ---- INSANE SCREEN SHAKE (LinkedIn) ---- */
@keyframes supershake { @keyframes supershake {
0% { 0% {
transform: translate(0, 0) rotate(0); transform: translate(0, 0) rotate(0);
} }
5% { 5% {
transform: translate(-20px, 5px) rotate(-2deg); transform: translate(-20px, 5px) rotate(-2deg);
} }
10% { 10% {
transform: translate(18px, -8px) rotate(2deg); transform: translate(18px, -8px) rotate(2deg);
} }
15% { 15% {
transform: translate(-22px, 8px) rotate(-4deg); transform: translate(-22px, 8px) rotate(-4deg);
} }
20% { 20% {
transform: translate(22px, -2px) rotate(4deg); transform: translate(22px, -2px) rotate(4deg);
} }
25% { 25% {
transform: translate(-18px, 12px) rotate(-2deg); transform: translate(-18px, 12px) rotate(-2deg);
} }
30% { 30% {
transform: translate(18px, -10px) rotate(2deg); transform: translate(18px, -10px) rotate(2deg);
} }
35% { 35% {
transform: translate(-22px, 14px) rotate(-4deg); transform: translate(-22px, 14px) rotate(-4deg);
} }
40% { 40% {
transform: translate(22px, -12px) rotate(4deg); transform: translate(22px, -12px) rotate(4deg);
} }
45% { 45% {
transform: translate(-18px, 8px) rotate(-2deg); transform: translate(-18px, 8px) rotate(-2deg);
} }
50% { 50% {
transform: translate(18px, -14px) rotate(4deg); transform: translate(18px, -14px) rotate(4deg);
} }
55% { 55% {
transform: translate(-22px, 12px) rotate(-4deg); transform: translate(-22px, 12px) rotate(-4deg);
} }
60% { 60% {
transform: translate(22px, -8px) rotate(2deg); transform: translate(22px, -8px) rotate(2deg);
} }
65% { 65% {
transform: translate(-18px, 10px) rotate(-2deg); transform: translate(-18px, 10px) rotate(-2deg);
} }
70% { 70% {
transform: translate(18px, -12px) rotate(2deg); transform: translate(18px, -12px) rotate(2deg);
} }
75% { 75% {
transform: translate(-22px, 14px) rotate(-4deg); transform: translate(-22px, 14px) rotate(-4deg);
} }
80% { 80% {
transform: translate(22px, -10px) rotate(4deg); transform: translate(22px, -10px) rotate(4deg);
} }
85% { 85% {
transform: translate(-18px, 8px) rotate(-2deg); transform: translate(-18px, 8px) rotate(-2deg);
} }
90% { 90% {
transform: translate(18px, -14px) rotate(2deg); transform: translate(18px, -14px) rotate(2deg);
} }
95% { 95% {
transform: translate(-20px, 5px) rotate(-2deg); transform: translate(-20px, 5px) rotate(-2deg);
} }
100% { 100% {
transform: translate(0, 0) rotate(0); transform: translate(0, 0) rotate(0);
} }
} }
.shake-screen { .shake-screen {
animation: supershake 1s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; animation: supershake 1s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
} }
/* ---- CRACK + COLLAPSE OVERLAY (X icon) ---- */ /* ---- CRACK + COLLAPSE OVERLAY (X icon) ---- */
.crack-overlay { .crack-overlay {
pointer-events: none; pointer-events: none;
position: fixed; position: fixed;
inset: 0; inset: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
z-index: 9999; z-index: 9999;
transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.5s; transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.5s;
will-change: transform, opacity; will-change: transform, opacity;
} }
.crack { .crack {
position: absolute; position: absolute;
pointer-events: none; pointer-events: none;
} }
.crack1 { .crack1 {
width: 35vw; width: 35vw;
left: 10vw; left: 10vw;
top: 22vh; top: 22vh;
opacity: 0.8; opacity: 0.8;
} }
.crack2 { .crack2 {
width: 32vw; width: 32vw;
right: 12vw; right: 12vw;
top: 42vh; top: 42vh;
opacity: 0.7; opacity: 0.7;
transform: rotate(-8deg); transform: rotate(-8deg);
} }
/* Add more .crackN classes if using more cracks */ /* Add more .crackN classes if using more cracks */
/* Collapse falling effect */ /* Collapse falling effect */
.crack-collapse { .crack-collapse {
transform: perspective(900px) rotateX(75deg) translateY(80vh) scale(0.9); transform: perspective(900px) rotateX(75deg) translateY(80vh) scale(0.9);
opacity: 0; opacity: 0;
transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.6s; transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.6s;
}
/* --- X Logo Crack-and-Spin Easter Egg --- */
.x-logo.cracked {
/* Overlay cracks with a clip-path or filters for effect (simple grayscale/contrast for demo) */
filter: grayscale(1) brightness(0.6) contrast(2);
/* Slight shake + scale for realism */
transform: scale(1.04);
transition: filter 0.2s, transform 0.2s;
}
.x-logo.spin {
animation: xspin180 0.8s cubic-bezier(0.77, 0, 0.18, 1) forwards;
}
@keyframes xspin180 {
0% {
transform: rotate(0deg) scale(1.04);
}
80% {
transform: rotate(200deg) scale(1.04);
}
100% {
transform: rotate(180deg) scale(1.04);
}
}
.x-logo.cracked {
filter: grayscale(1) brightness(0.7) contrast(2.6);
transition: filter 0.2s, transform 0.2s;
}
.x-logo.spin {
animation: xspin180 0.8s cubic-bezier(0.77, 0, 0.18, 1) forwards;
}
.x-logo.nospin {
transform: rotate(0deg) scale(1.0);
filter: none;
transition: none;
}
.x-logo-cracks {
pointer-events: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 2;
}
.x-logo-cracks img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
opacity: 0.55;
pointer-events: none;
}
.footer-x-logo-wrap {
position: relative;
display: inline-block;
width: 28px;
height: 28px;
}
/* === X Logo Full Page Crack-and-Spin Easter Egg === */
.body-cracked .crack-overlay,
.body-cracked .crack1,
.body-cracked .crack2 {
pointer-events: none;
}
.body-cracked {
/* Animate the whole page spin with a transform on html/body! */
animation: bodyspin180 0.8s cubic-bezier(0.77, 0, 0.18, 1) forwards;
will-change: transform;
}
.body-spin-back {
/* Immediately resets the transform, no animation needed */
animation: none !important;
transform: rotate(0deg) !important;
}
@keyframes bodyspin180 {
0% {
transform: rotate(0deg);
}
80% {
transform: rotate(200deg);
}
100% {
transform: rotate(180deg);
}
} }

View File

@ -2,79 +2,100 @@ import React, { useCallback, useRef, useState } from "react";
import Link from "next/link"; import Link from "next/link";
export default function BottomFooter() { export default function BottomFooter() {
// ig easter egg // Instagram Lava Flood Easter Egg
const [lavaActive, setLavaActive] = useState(false); const [lavaActive, setLavaActive] = useState(false);
const lavaTimeout = useRef<any>(null); const lavaTimeout = useRef<any>(null);
// LinkedIn easter egg // Instagram sound
const earthquakeAudio = useRef<HTMLAudioElement | null>(null);
// LinkedIn Shake Easter Egg
const [shaking, setShaking] = useState(false); const [shaking, setShaking] = useState(false);
const shakeTimeout = useRef<any>(null); const shakeTimeout = useRef<any>(null);
// x easter egg // X Logo Full-Page Crack & Spin Easter Egg
const [showCracks, setShowCracks] = useState(false); const [showCracks, setShowCracks] = useState(false);
const [collapse, setCollapse] = useState(false); const [crackOverlayActive, setCrackOverlayActive] = useState(false);
const crackTimeout = useRef<any>(null);
// Lava flood handler (top-down flood) // Lava flood & sound handler
const handleInstagramClick = useCallback((e: React.MouseEvent) => { const handleInstagramClick = useCallback((e: React.MouseEvent) => {
e.preventDefault(); e.preventDefault();
setLavaActive(true); setLavaActive(true);
// Play earthquake sound
if (earthquakeAudio.current) {
earthquakeAudio.current.currentTime = 0;
earthquakeAudio.current.play();
}
clearTimeout(lavaTimeout.current); clearTimeout(lavaTimeout.current);
lavaTimeout.current = setTimeout(() => setLavaActive(false), 2000); lavaTimeout.current = setTimeout(() => setLavaActive(false), 2000);
}, []); }, []);
// LinkedIn shake handler // LinkedIn shake handler
const handleLinkedInClick = useCallback((e: React.MouseEvent) => { const handleLinkedInClick = useCallback(
e.preventDefault(); (e: React.MouseEvent) => {
if (shaking) return; // prevent stacking e.preventDefault();
setShaking(true); if (shaking) return; // prevent stacking
const body = document.body; setShaking(true);
body.classList.remove("shake-screen"); const body = document.body;
void body.offsetWidth;
body.classList.add("shake-screen");
shakeTimeout.current = setTimeout(() => {
setShaking(false);
body.classList.remove("shake-screen"); body.classList.remove("shake-screen");
}, 1000); void body.offsetWidth;
}, [shaking]); body.classList.add("shake-screen");
shakeTimeout.current = setTimeout(() => {
setShaking(false);
body.classList.remove("shake-screen");
}, 1000);
},
[shaking]
);
// X (crack and collapse) handler // X click = crack, spin page, then reset after 2s
const handleXClick = useCallback((e: React.MouseEvent) => { const handleXClick = useCallback(
e.preventDefault(); (e: React.MouseEvent) => {
setShowCracks(true); e.preventDefault();
crackTimeout.current = setTimeout(() => { if (crackOverlayActive) return;
setCollapse(true); setShowCracks(true);
setCrackOverlayActive(true);
document.body.classList.remove("body-spin-back");
document.body.classList.add("body-cracked");
setTimeout(() => { setTimeout(() => {
setShowCracks(false); document.body.classList.remove("body-cracked");
setCollapse(false); document.body.classList.add("body-spin-back");
}, 1500); setTimeout(() => {
}, 1000); setShowCracks(false);
}, []); setCrackOverlayActive(false);
document.body.classList.remove("body-spin-back");
}, 200);
}, 2000);
},
[crackOverlayActive]
);
// Clean up classes and timeouts on unmount
React.useEffect(() => { React.useEffect(() => {
return () => { return () => {
clearTimeout(lavaTimeout.current); clearTimeout(lavaTimeout.current);
clearTimeout(shakeTimeout.current); clearTimeout(shakeTimeout.current);
clearTimeout(crackTimeout.current);
document.body.classList.remove("shake-screen"); document.body.classList.remove("shake-screen");
document.body.classList.remove("body-cracked");
document.body.classList.remove("body-spin-back");
}; };
}, []); }, []);
return ( return (
<> <>
{/* Hidden audio element */}
<audio ref={earthquakeAudio} src="/earthquake.mp3" preload="auto" />
{/* Lava Flood Overlay */} {/* Lava Flood Overlay */}
{lavaActive && ( {lavaActive && (
<div className="lava-flood-overlay lava-active"> <div className="lava-flood-overlay lava-active">
<img src="/lava.jpg" alt="Lava flood" draggable={false} /> <img src="/lava.jpg" alt="Lava flood" draggable={false} />
</div> </div>
)} )}
{/* Crack overlay for the spinning effect */}
{/* Crack & Collapse Overlay */} {showCracks && (
{(showCracks || collapse) && ( <div className="crack-overlay" style={{ pointerEvents: "none" }}>
<div className={`crack-overlay${collapse ? " crack-collapse" : ""}`}>
<img className="crack crack1" src="/crack1.png" alt="" /> <img className="crack crack1" src="/crack1.png" alt="" />
<img className="crack crack2" src="/crack2.png" alt="" /> <img className="crack crack2" src="/crack2.png" alt="" />
{/* Add more cracks for extra effect if you wish */}
</div> </div>
)} )}
@ -113,7 +134,6 @@ export default function BottomFooter() {
</li> </li>
</ul> </ul>
</div> </div>
{/* Donate Section */} {/* Donate Section */}
<div className="min-w-[220px] mb-8 md:mb-0 flex-1"> <div className="min-w-[220px] mb-8 md:mb-0 flex-1">
<h3 className="font-bold underline text-lg mb-3">Donate</h3> <h3 className="font-bold underline text-lg mb-3">Donate</h3>
@ -128,17 +148,16 @@ export default function BottomFooter() {
</Link> </Link>
</div> </div>
</div> </div>
{/* Bottom bar */} {/* Bottom bar */}
<div className="max-w-6xl mx-auto mt-8 pt-6 flex flex-col md:flex-row items-center justify-between border-t border-gray-200/30"> <div className="max-w-6xl mx-auto mt-8 pt-6 flex flex-col md:flex-row items-center justify-between border-t border-gray-200/30">
<div className="flex flex-row items-center w-full md:w-auto"> <div className="flex flex-row items-center w-full md:w-auto">
<img <img
src="/logo.png" src="/logo.png"
alt="TremorTracker logo" alt="TremorTracker logo"
className="h-16 w-auto mr-4 object-contain" className="h-16 w-auto mr-4 object-contain"
style={{ maxHeight: 75 }} style={{ maxHeight: 75 }}
/> />
</div> </div>
<span className="text-sm flex items-center"> <span className="text-sm flex items-center">
<span className="mr-2">&#169;</span> TremorTracker 2025 <span className="mr-2">&#169;</span> TremorTracker 2025
</span> </span>
@ -167,7 +186,14 @@ export default function BottomFooter() {
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
aria-label="X Crack Easter egg" aria-label="X Crack Easter egg"
> >
<img src="x_logo.jpg" alt="X" className="h-7 w-7 rounded-full shadow" /> <span className="footer-x-logo-wrap">
<img
src="x_logo.jpg"
alt="X"
className="h-7 w-7 rounded-full shadow"
style={{ display: "block"}}
/>
</span>
</a> </a>
</div> </div>
</div> </div>