diff --git a/src/app/page.tsx b/src/app/page.tsx
index 36246d7..c4f56c7 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -11,7 +11,8 @@ import { MiniProjectModal } from "../components/MiniProjectModal";
import { Navbar } from "../components/Navbar";
import { Footer } from "../sections/Footer";
import { TechStack } from "../sections/TechStack";
-import { useState } from "react";
+import { TypingRoomIntro } from "../components/TypingRoomIntro";
+import { useCallback, useEffect, useState } from "react";
import type { Experience } from "../types";
export default function Home() {
@@ -24,12 +25,21 @@ export default function Home() {
} = useProfile();
const [showOldNames, setShowOldNames] = useState(false);
+ const [showTypingIntro, setShowTypingIntro] = useState(true);
const oldUsernames = [
"getspaced (ingame)",
"Space (alternative)",
"Space-Banane (2022-2024)",
];
+ useEffect(() => {
+ // keep intro visible on every full page load; no-op here
+ }, []);
+
+ const handleIntroFinish = useCallback(() => {
+ setShowTypingIntro(false);
+ }, []);
+
const groupedExperiences = experiences.reduce(
(acc, exp) => {
if (!acc[exp.type]) acc[exp.type] = [];
@@ -41,95 +51,100 @@ export default function Home() {
return (
<>
-
-
-
+
+ {!showTypingIntro && (
+ <>
+
+
+
-
+
-
+
-
-
-
- Featured Projects
-
-
- A selection of my personal favorites. Many more on my GitHub.
-
-
-
- {projects.map((project, index) => (
-
- ))}
-
-
-
-
-
-
More Projects
-
Smaller projects or tools I've built.
-
-
- {miniProjects.map((project, index) => (
- setSelectedMiniProject(project)}
- />
- ))}
-
-
-
-
-
-
Skills & Experience
-
Things I've worked with over the years.
-
-
- {Object.entries(groupedExperiences).map(([type, items]) => (
-
-
- {type}
-
-
- {items.map((exp, index) => (
- setSelectedExperience(exp)}
- />
- ))}
-
+
+
+
+ Featured Projects
+
+
+ A selection of my personal favorites. Many more on my GitHub.
+
- ))}
+
+ {projects.map((project, index) => (
+
+ ))}
+
+
+
+
+
+
More Projects
+
Smaller projects or tools I've built.
+
+
+ {miniProjects.map((project, index) => (
+ setSelectedMiniProject(project)}
+ />
+ ))}
+
+
+
+
+
+
Skills & Experience
+
Things I've worked with over the years.
+
+
+ {Object.entries(groupedExperiences).map(([type, items]) => (
+
+
+ {type}
+
+
+ {items.map((exp, index) => (
+ setSelectedExperience(exp)}
+ />
+ ))}
+
+
+ ))}
+
+
-
-
-
+
- {selectedMiniProject && (
- setSelectedMiniProject(null)}
- />
- )}
- {selectedExperience && (
- setSelectedExperience(null)}
- />
+ {selectedMiniProject && (
+ setSelectedMiniProject(null)}
+ />
+ )}
+ {selectedExperience && (
+ setSelectedExperience(null)}
+ />
+ )}
+ >
)}
>
);
diff --git a/src/components/TypingRoomIntro.tsx b/src/components/TypingRoomIntro.tsx
new file mode 100644
index 0000000..a38d524
--- /dev/null
+++ b/src/components/TypingRoomIntro.tsx
@@ -0,0 +1,220 @@
+"use client";
+
+import { AnimatePresence, motion } from "framer-motion";
+import { useEffect, useRef, useState } from "react";
+
+interface TypingRoomIntroProps {
+ active: boolean;
+ onFinish: () => void;
+}
+
+export function TypingRoomIntro({ active, onFinish }: TypingRoomIntroProps) {
+ const [step, setStep] = useState(0);
+ const [garble, setGarble] = useState("");
+ const garbleRef = useRef(null);
+
+ useEffect(() => {
+ if (!active) {
+ setStep(0);
+ return;
+ }
+
+ const timers = [
+ window.setTimeout(() => setStep(1), 280),
+ window.setTimeout(() => setStep(2), 900),
+ window.setTimeout(() => setStep(3), 1700),
+ window.setTimeout(() => setStep(4), 2800),
+ window.setTimeout(onFinish, 3600),
+ ];
+
+ return () => {
+ timers.forEach((timer) => window.clearTimeout(timer));
+ };
+ }, [active, onFinish]);
+
+ // Garbled typing generator while Space is "typing"
+ useEffect(() => {
+ if (step >= 3 && step < 4) {
+ setGarble("");
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()[]{}<>~-=_+";
+ garbleRef.current = window.setInterval(() => {
+ // create a short random string to simulate garble
+ const len = 20;
+ let s = "";
+ for (let i = 0; i < len; i++) {
+ s += charset[Math.floor(Math.random() * charset.length)];
+ }
+ setGarble(s);
+ }, 120);
+ } else {
+ if (garbleRef.current) {
+ window.clearInterval(garbleRef.current);
+ garbleRef.current = null;
+ }
+ setGarble("");
+ }
+
+ return () => {
+ if (garbleRef.current) {
+ window.clearInterval(garbleRef.current);
+ garbleRef.current = null;
+ }
+ };
+ }, [step]);
+
+ useEffect(() => {
+ if (!active) {
+ return;
+ }
+
+ const originalOverflow = document.body.style.overflow;
+ document.body.style.overflow = "hidden";
+
+ return () => {
+ document.body.style.overflow = originalOverflow;
+ };
+ }, [active]);
+
+ useEffect(() => {
+ if (!active) {
+ return;
+ }
+
+ const handleEscape = (event: KeyboardEvent) => {
+ if (event.key === "Escape") {
+ onFinish();
+ }
+ };
+
+ window.addEventListener("keydown", handleEscape);
+
+ return () => {
+ window.removeEventListener("keydown", handleEscape);
+ };
+ }, [active, onFinish]);
+
+ return (
+
+ {active && (
+
+
+
+
+
+
+
+
+
+
+
+
+ Orbital Chat
+
+
+ live
+
+
+
+
+
+ Breaking Websockets...
+
+
+ {step >= 1 && (
+
+ Why is this page taking so long to load??
+
+ )}
+
+ {step >= 2 && (
+
+ I have to somehow hide the fact that my discord status is taking a while to load... :D
+
+ )}
+
+ {step >= 4 && (
+
+ Ok done, just one more animation
+
+ )}
+
+
+
+ {/* Small badge left-above the input showing typing status (hidden once final message shows) */}
+ {step < 4 && (
+
+
Space is typing...
+
+ {[0, 1, 2].map((dot) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+ You & Space
+
+ Esc to skip
+
+
+
+ {step >= 3 && step < 4 ? (
+
+ {garble || "…"}
+
+ ) : (
+
Message Space
+ )}
+
+
+
+ = 4 ? 1 : step / 4 }}
+ transition={{ duration: 0.45, ease: "easeOut" }}
+ className="h-1 origin-left bg-gradient-to-r from-cyan-300 via-blue-400 to-cyan-300"
+ />
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/context/ProfileContext.tsx b/src/context/ProfileContext.tsx
index dc219c2..69dfcdb 100644
--- a/src/context/ProfileContext.tsx
+++ b/src/context/ProfileContext.tsx
@@ -42,18 +42,17 @@ export function ProfileProvider({ children }: { children: React.ReactNode }) {
const rotatingMessages = useMemo(
() => [
- "Yelling at Claude",
- "Shipping bugs",
- "Compiling typescript files... Please wait.",
- "Waiting for CI, this might take a while...",
- "No debugger attached, but I'm sure it works ?",
- "Seems fine, must be a problem with your machine lol",
- "Waiting for pip, this might take a lifetime...",
- "Collecting Spotify hours, this might take a while...",
- "Fighting with go modules, i lost",
- "Nuking Python for the 50th time, it's winning",
- "Why is uv so cool?",
- "Pulling 20-ish GB of node modules, see you next year",
+ "Yelling at Luna",
+ "Assigning more Issues to myself and letting Luna do them",
+ "NOT coding in Rust 😂✌️",
+ "Rewriting the same helper 3 times",
+ "Micro-service-maxxing",
+ "Blaming SHSF for my bad code",
+ "Shipping fast SHSF code",
+ "Writing code with 1.4k+ Lines",
+ "Love-Hate relationship with TypeScript",
+ "Blaming Openai",
+ "Undescribable Music Taste"
],
[],
);