105 lines
4.1 KiB
TypeScript
105 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
const TRAIN_ASCII = ` ==== ________ ___________
|
|
_D _| |_______/ \\__I_I_____===__|_________|
|
|
|(_)--- | H\\________/ | | =|___ ___| _________________ _________________ _________________ _________________ _________________ _________________
|
|
/ | | H | | | | ||_| |_|| _| \\_____| \\_____| \\_____| \\_____| \\_____| \\_____A
|
|
| | | H |__--------------------| [___] | =| | | | | | |
|
|
| ________|___H__/__|_____/[][]~\\_______| | -| | | | | | |
|
|
|/ | |-----------I_____I [][] [] D |=======|____|________________________|_______________________|_______________________|_______________________|_______________________|_______________________|_
|
|
__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__|__________________________|_______________________|_______________________|_______________________|_______________________|_______________________|
|
|
|/-=|___|= || || || |_____/~\\___/ |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_|
|
|
\\_/ \\O=====O=====O=====O_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/`;
|
|
|
|
export default function SlBackground() {
|
|
const [pos, setPos] = useState(150);
|
|
|
|
useEffect(() => {
|
|
let animationFrameId: number;
|
|
let lastTime = 0;
|
|
|
|
const animate = (timestamp: number) => {
|
|
if (!lastTime) lastTime = timestamp;
|
|
const delta = timestamp - lastTime;
|
|
|
|
// Update position every ~30ms to create a retro sliding terminal refresh feel
|
|
if (delta > 45) {
|
|
setPos((prev) => {
|
|
if (prev <= -150) return 150;
|
|
return prev - 1.5;
|
|
});
|
|
lastTime = timestamp;
|
|
}
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
};
|
|
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
return () => cancelAnimationFrame(animationFrameId);
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<style>
|
|
{`
|
|
@keyframes rainbowShift {
|
|
0% { background-position: 0% 50%; }
|
|
50% { background-position: 100% 50%; }
|
|
100% { background-position: 0% 50%; }
|
|
}
|
|
`}
|
|
</style>
|
|
<div
|
|
style={{
|
|
position: "fixed",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
pointerEvents: "none",
|
|
zIndex: 0,
|
|
overflow: "hidden",
|
|
display: "flex",
|
|
alignItems: "flex-end",
|
|
paddingBottom: "5vh",
|
|
justifyContent: "center",
|
|
userSelect: "none",
|
|
backgroundImage: "linear-gradient(rgba(29, 32, 33, 0.94), rgba(29, 32, 33, 0.94)), url('/gruvbox.webp')",
|
|
backgroundSize: "cover",
|
|
backgroundPosition: "center",
|
|
}}
|
|
>
|
|
<pre
|
|
style={{
|
|
margin: 0,
|
|
whiteSpace: "pre",
|
|
fontFamily: "var(--font-custom-mono), monospace",
|
|
fontSize: "1rem",
|
|
transform: "translateX(" + pos + "vw)",
|
|
willChange: "transform",
|
|
background: "linear-gradient(90deg, var(--red), var(--accent2), var(--accent), var(--green), var(--aqua), var(--blue), var(--purple))",
|
|
backgroundSize: "200% auto",
|
|
WebkitBackgroundClip: "text",
|
|
WebkitTextFillColor: "transparent",
|
|
animation: "rainbowShift 3s linear infinite",
|
|
opacity: 0.35,
|
|
filter: "drop-shadow(0 0 12px rgba(250, 189, 47, 0.5))",
|
|
}}
|
|
>
|
|
{TRAIN_ASCII}
|
|
</pre>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|