Easter Eggs

This commit is contained in:
IZZY 2025-05-31 17:54:56 +01:00
parent 587b6b7f01
commit 4438953fab
8 changed files with 349 additions and 87 deletions

BIN
public/crack1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
public/crack2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/lava.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -65,3 +65,166 @@ body {
color: #111; color: #111;
/* or black */ /* or black */
} }
/* ---- LAVA FLOOD OVERLAY ---- */
.lava-flood-overlay {
pointer-events: none;
position: fixed;
top: -100vh;
left: 0;
right: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
display: flex;
justify-content: center;
align-items: flex-start;
transition: top 0.9s cubic-bezier(.6, 0, .2, 1);
}
.lava-flood-overlay.lava-active {
top: 0;
transition: top 0.33s cubic-bezier(.6, 0, .2, 1);
}
.lava-flood-overlay img,
.lava-gradient {
width: 100vw;
height: 100vh;
object-fit: cover;
pointer-events: none;
user-select: none;
filter: brightness(1.15) saturate(1.8) drop-shadow(0 0 80px #ff5500);
}
/* ---- INSANE SCREEN SHAKE (LinkedIn) ---- */
@keyframes supershake {
0% {
transform: translate(0, 0) rotate(0);
}
5% {
transform: translate(-20px, 5px) rotate(-2deg);
}
10% {
transform: translate(18px, -8px) rotate(2deg);
}
15% {
transform: translate(-22px, 8px) rotate(-4deg);
}
20% {
transform: translate(22px, -2px) rotate(4deg);
}
25% {
transform: translate(-18px, 12px) rotate(-2deg);
}
30% {
transform: translate(18px, -10px) rotate(2deg);
}
35% {
transform: translate(-22px, 14px) rotate(-4deg);
}
40% {
transform: translate(22px, -12px) rotate(4deg);
}
45% {
transform: translate(-18px, 8px) rotate(-2deg);
}
50% {
transform: translate(18px, -14px) rotate(4deg);
}
55% {
transform: translate(-22px, 12px) rotate(-4deg);
}
60% {
transform: translate(22px, -8px) rotate(2deg);
}
65% {
transform: translate(-18px, 10px) rotate(-2deg);
}
70% {
transform: translate(18px, -12px) rotate(2deg);
}
75% {
transform: translate(-22px, 14px) rotate(-4deg);
}
80% {
transform: translate(22px, -10px) rotate(4deg);
}
85% {
transform: translate(-18px, 8px) rotate(-2deg);
}
90% {
transform: translate(18px, -14px) rotate(2deg);
}
95% {
transform: translate(-20px, 5px) rotate(-2deg);
}
100% {
transform: translate(0, 0) rotate(0);
}
}
.shake-screen {
animation: supershake 1s cubic-bezier(.36, .07, .19, .97) both;
}
/* ---- CRACK + COLLAPSE OVERLAY (X icon) ---- */
.crack-overlay {
pointer-events: none;
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
transition: transform 1.1s cubic-bezier(.65, .05, .45, 1), opacity 0.5s;
will-change: transform, opacity;
}
.crack {
position: absolute;
pointer-events: none;
}
.crack1 {
width: 35vw;
left: 10vw;
top: 22vh;
opacity: 0.8;
}
.crack2 {
width: 32vw;
right: 12vw;
top: 42vh;
opacity: 0.7;
transform: rotate(-8deg);
}
/* Add more .crackN classes if using more cracks */
/* Collapse falling effect */
.crack-collapse {
transform: perspective(900px) rotateX(75deg) translateY(80vh) scale(0.9);
opacity: 0;
transition: transform 1.1s cubic-bezier(.65, .05, .45, 1), opacity 0.6s;
}

View File

@ -55,7 +55,7 @@ export default function LearnPage() {
<li>First aid kit and emergency medication</li> <li>First aid kit and emergency medication</li>
<li>Food (non-perishable)</li> <li>Food (non-perishable)</li>
<li>Bottled water</li> <li>Bottled water</li>
<li>Torch (flashlight)</li> <li>Torch</li>
<li>Satellite phone</li> <li>Satellite phone</li>
<li>Warm clothing and blankets</li> <li>Warm clothing and blankets</li>
</ul> </ul>

View File

@ -23,17 +23,16 @@ export default function Observatories() {
() => () =>
data && data.observatories data && data.observatories
? data.observatories ? data.observatories
.map( .map((x: Observatory): GeologicalEvent & { isFunctional: boolean } => ({
(x: Observatory): GeologicalEvent => ({
id: x.id.toString(), id: x.id.toString(),
title: `New Observatory - ${x.name}`, title: ` ${x.name}`,
longitude: x.longitude, longitude: x.longitude,
latitude: x.latitude, latitude: x.latitude,
isFunctional: x.isFunctional, // <-- include this!
text1: "", text1: "",
text2: getRelativeDate(x.dateEstablished), text2: getRelativeDate(x.dateEstablished),
date: x.dateEstablished, date: x.dateEstablished,
}) }))
)
.sort( .sort(
(a: GeologicalEvent, b: GeologicalEvent) => (a: GeologicalEvent, b: GeologicalEvent) =>
new Date(b.date).getTime() - new Date(a.date).getTime() new Date(b.date).getTime() - new Date(a.date).getTime()

View File

@ -1,9 +1,84 @@
// components/Footer.tsx import React, { useCallback, useRef, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { FaFacebook, FaLinkedin, FaTwitter, FaYoutube } from "react-icons/fa";
export default function Footer() { export default function BottomFooter() {
// Lava flood state & timer
const [lavaActive, setLavaActive] = useState(false);
const lavaTimeout = useRef<any>(null);
// LinkedIn shake state & timer
const [shaking, setShaking] = useState(false);
const shakeTimeout = useRef<any>(null);
// Crack+collapse states for the X logo
const [showCracks, setShowCracks] = useState(false);
const [collapse, setCollapse] = useState(false);
const crackTimeout = useRef<any>(null);
// Lava flood handler (top-down flood)
const handleInstagramClick = useCallback((e: React.MouseEvent) => {
e.preventDefault();
setLavaActive(true);
clearTimeout(lavaTimeout.current);
lavaTimeout.current = setTimeout(() => setLavaActive(false), 2000);
}, []);
// LinkedIn shake handler
const handleLinkedInClick = useCallback((e: React.MouseEvent) => {
e.preventDefault();
if (shaking) return; // prevent stacking
setShaking(true);
const body = document.body;
body.classList.remove("shake-screen");
void body.offsetWidth;
body.classList.add("shake-screen");
shakeTimeout.current = setTimeout(() => {
setShaking(false);
body.classList.remove("shake-screen");
}, 1000);
}, [shaking]);
// X (crack and collapse) handler
const handleXClick = useCallback((e: React.MouseEvent) => {
e.preventDefault();
setShowCracks(true);
crackTimeout.current = setTimeout(() => {
setCollapse(true);
setTimeout(() => {
setShowCracks(false);
setCollapse(false);
}, 1500);
}, 1000);
}, []);
React.useEffect(() => {
return () => {
clearTimeout(lavaTimeout.current);
clearTimeout(shakeTimeout.current);
clearTimeout(crackTimeout.current);
document.body.classList.remove("shake-screen");
};
}, []);
return ( return (
<>
{/* Lava Flood Overlay */}
{lavaActive && (
<div className="lava-flood-overlay lava-active">
<img src="/lava.jpg" alt="Lava flood" draggable={false} />
</div>
)}
{/* Crack & Collapse Overlay */}
{(showCracks || collapse) && (
<div className={`crack-overlay${collapse ? " crack-collapse" : ""}`}>
<img className="crack crack1" src="/crack1.png" alt="" />
<img className="crack crack2" src="/crack2.png" alt="" />
{/* Add more cracks for extra effect if you wish */}
</div>
)}
{/* Footer */}
<footer className="bg-[#16424b] text-white pt-12 pb-4 px-6 mt-12 z-0"> <footer className="bg-[#16424b] text-white pt-12 pb-4 px-6 mt-12 z-0">
<div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between gap-8"> <div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between gap-8">
{/* Useful Links */} {/* Useful Links */}
@ -21,17 +96,24 @@ export default function Footer() {
</Link> </Link>
</li> </li>
<li> <li>
<Link href="https://www.dysoninstitute.ac.uk/about-us/governance/privacy-notices/" className="hover:underline"> <Link
href="https://www.dysoninstitute.ac.uk/about-us/governance/privacy-notices/"
className="hover:underline"
>
Privacy policy Privacy policy
</Link> </Link>
</li> </li>
<li> <li>
<Link href="https://privacy.dyson.com/en/globalcookiepolicy.aspx" className="hover:underline"> <Link
href="https://privacy.dyson.com/en/globalcookiepolicy.aspx"
className="hover:underline"
>
Cookies policy Cookies policy
</Link> </Link>
</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>
@ -39,36 +121,50 @@ export default function Footer() {
We are a nonprofit entirely funded by your donations, every penny helps provide life saving information. We are a nonprofit entirely funded by your donations, every penny helps provide life saving information.
</p> </p>
<Link <Link
href="#" href="https://shelterbox.org/"
className="bg-gray-200 hover:bg-blue-600 hover:text-white text-black font-bold rounded-full px-8 py-2 shadow transition-colors duration-200 inline-block text-center" className="bg-gray-200 hover:bg-blue-600 hover:text-white text-black font-bold rounded-full px-8 py-2 shadow transition-colors duration-200 inline-block text-center"
> >
Donate Now Donate Now
</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">
{/* Bottom left: Copyright */}
<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>
{/* Bottom right: Social icons */}
<div className="flex flex-col items-end"> <div className="flex flex-col items-end">
<span className="text-sm mb-2">Follow us on</span> <span className="text-sm mb-2">Follow us on</span>
<div className="flex space-x-3"> <div className="flex space-x-3">
{/* Replace src with your icon URLs, or use next/image if preferred */} <a
<a href="#" target="_blank" rel="noopener noreferrer"> href="#"
onClick={handleInstagramClick}
style={{ cursor: "pointer" }}
aria-label="Instagram Lava Easter egg"
>
<img src="instagram.png" alt="instagram" className="h-7 w-7 rounded-full shadow" /> <img src="instagram.png" alt="instagram" className="h-7 w-7 rounded-full shadow" />
</a> </a>
<a href="#" target="_blank" rel="noopener noreferrer"> <a
href="#"
onClick={handleLinkedInClick}
style={{ cursor: "pointer" }}
aria-label="LinkedIn Shake Easter egg"
>
<img src="linkedin.png" alt="linkedin" className="h-7 w-7 rounded-full shadow" /> <img src="linkedin.png" alt="linkedin" className="h-7 w-7 rounded-full shadow" />
</a> </a>
<a href="#" target="_blank" rel="noopener noreferrer"> <a
href="#"
onClick={handleXClick}
style={{ cursor: "pointer" }}
aria-label="X Crack Easter egg"
>
<img src="x_logo.jpg" alt="X" className="h-7 w-7 rounded-full shadow" /> <img src="x_logo.jpg" alt="X" className="h-7 w-7 rounded-full shadow" />
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>
</>
); );
} }

View File

@ -112,7 +112,11 @@ function MapComponent({
const observatoryElement = document.createElement("div"); const observatoryElement = document.createElement("div");
const root = createRoot(observatoryElement); const root = createRoot(observatoryElement);
root.render(<GiObservatory className="text-blue-600 text-2xl drop-shadow-lg" />); root.render(
<GiObservatory
className={`text-2xl drop-shadow-lg ${event.isFunctional === false ? "text-gray-400" : "text-blue-600"}`}
/>
);
quakeElement.appendChild(pulseElement); quakeElement.appendChild(pulseElement);
quakeElement.appendChild(dotElement); quakeElement.appendChild(dotElement);