Files
my-portfolio/app/routes/_index.tsx
2025-04-26 16:25:38 +02:00

595 lines
21 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { MetaFunction } from "@remix-run/node";
import { useEffect, useState } from "react";
import { json } from "@remix-run/node";
import { useLoaderData, Link } from "@remix-run/react";
export const meta: MetaFunction = () => {
return [
{ title: "Paul W." },
{ name: "description", content: "This is my Profile!" },
{
name: "keywords",
content: "Paul W, Paul W Portfolio, Paul W Profile, Paul W Remix",
},
];
};
interface BlogPost {
id: string;
title: string;
excerpt: string;
date: string;
access: string; // Where its located in the folder
tags: string[];
hide_date?: boolean;
}
export async function loader() {
const posts: BlogPost[] = [
{
id: "1",
title: "My First Post",
excerpt: "Yeah, as it says, this is my first post.",
date: "2024-01-23",
access: "myfirstpost",
tags: ["Hello, World!"],
hide_date: true,
},
// Add more posts here
];
return json({ posts });
}
export default function Index() {
const { posts } = useLoaderData<typeof loader>();
const [status, setStatus] = useState("");
const [border_status, setBorderStatus] = useState("border-gray-700");
useEffect(() => {
if (status === "online") {
setBorderStatus("border-green-500");
} else if (status === "offline") {
setBorderStatus("border-red-500");
} else if (status === "dnd") {
setBorderStatus("border-red-600");
} else if (status === "idle") {
setBorderStatus("border-yellow-500");
} else {
setBorderStatus("border-gray-700");
}
}, [status]);
useEffect(() => {
fetch(
"https://shsf-api.cottonfieldworkers.shop/api/exec/6/c084ec4a-1b20-491e-ab2e-67c5fa8881e6"
)
.then((res) => res.json())
.then((data) => {
setStatus(data.status);
})
.catch((err) => {
console.error(err);
setStatus("offline");
});
}, []);
return (
<div className="relative flex min-h-screen flex-col items-center justify-center p-8 overflow-hidden">
<div className="absolute inset-0 z-0">
<div className="h-full w-full bg-black">
<div
className="absolute inset-0"
style={{
backgroundImage:
"linear-gradient(#ffffff15 1px, transparent 1px), linear-gradient(90deg, #ffffff15 1px, transparent 1px)",
backgroundSize: "20px 20px",
}}
/>
</div>
</div>
<div className="relative z-10 flex flex-col items-center gap-8 mt-12 max-w-6xl w-full">
{/* Hero */}
<div className="text-center space-y-6">
<div className="space-y-2">
<h1
className="text-5xl md:text-6xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent"
style={{ lineHeight: "normal" }}
>
Hey, I'm Paul!
</h1>
<h2 className="text-3xl md:text-4xl text-gray-400">
also known as{" "}
<span className="font-semibold text-gray-200">Space</span>
</h2>
</div>
<div
className={`w-48 h-48 mx-auto overflow-hidden rounded-full border-4 transition-colors duration-300 ${border_status}`}
>
<img
src="https://cdn.reversed.dev/pictures/20250405_120402.png"
alt="Paul W"
className="object-cover rounded-full shadow-lg scale-125"
/>
</div>
<div>
<p className="text-2xl text-gray-300">Self-proclaimed Developer</p>
</div>
</div>
{/* About Section */}
<section className="w-full">
<h2 className="text-3xl font-bold mb-8 text-center text-gray-100 bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
About Me
</h2>
<div
className="p-8 rounded-xl border border-gray-700 bg-white/5 backdrop-blur-lg
flex flex-col md:flex-row gap-8 items-center"
>
<div className="flex-1 space-y-6">
<p className="text-gray-300 text-lg leading-relaxed">
Hey! I'm <span className="underline font-bold">Paul</span>, a
passionate Developer from{" "}
<span className="bg-gradient-to-r from-gray-800 via-red-500 to-yellow-400 font-bold bg-clip-text text-transparent">
Germany
</span>{" "}
🇩🇪.
<span className="block mt-4">
I specialize in JavaScript and Node.js development, crafting
digital experiences that make a difference. When I'm not
coding, you'll find me gaming or scrolling through my
Instagram feed.
</span>
</p>
<div className="bg-white/5 p-6 rounded-lg backdrop-blur-sm">
<h3 className="text-xl font-bold text-gray-100 mb-4 bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">
About Me
</h3>
<div className="grid grid-cols-4 gap-4">
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">🤓</span>
<span>Nerd</span>
</div>
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">🎮</span>
<span>Gamer</span>
</div>
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">🛠</span>
<span>Developer</span>
</div>
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">👥</span>
<span>Introvert</span>
</div>
</div>
</div>
</div>
</div>
</section>
{/* My Blog Posts */}
<section className="w-full">
<h2
className="text-3xl font-bold mb-2 text-center text-gray-100 bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent"
style={{ lineHeight: "normal" }}
>
Blog Posts
</h2>
<p className="text-gray-300 text-lg mb-4 text-center">
Man i love this posting thing, can't you tell?
</p>
<div
className="p-8 rounded-xl border border-gray-700 bg-white/5 backdrop-blur-lg
flex flex-col md:flex-row gap-8 items-center"
>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8 text-center">
{posts.map((post) => (
<article
key={post.id}
className="p-6 rounded-xl border border-gray-700 bg-gray-800
transform transition-all duration-300 hover:scale-105 hover:shadow-xl
backdrop-blur-sm hover:bg-opacity-90"
>
<div className="flex flex-wrap gap-2 mb-3">
{!post.hide_date && (
<time className="text-sm text-gray-500">
{new Date(post.date).toLocaleDateString()}
</time>
)}
{post.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 text-sm bg-gray-900 text-gray-100 rounded-full"
>
{tag}
</span>
))}
</div>
<h3 className="text-xl font-bold mb-3 text-gray-100">
{post.title}
</h3>
<p className=" text-gray-400 text-base mb-4">
{post.excerpt}
</p>
<Link
to={`/blog/${post.access}`}
className="inline-flex items-center px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500
text-white rounded-full font-medium transition-all duration-300
hover:from-blue-600 hover:to-purple-600 hover:shadow-lg"
>
Read More
<svg
className="w-4 h-4 ml-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M14 5l7 7m0 0l-7 7m7-7H3"
/>
</svg>
</Link>
</article>
))}
</div>
</div>
</section>
{/* My Dawg */}
<section className="w-full">
<h2
className="text-3xl font-bold mb-8 text-center text-gray-100 bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent"
style={{ lineHeight: "normal" }}
>
Ma Dawg
</h2>
<div className="p-4 sm:p-8 rounded-xl border border-gray-700 bg-white/5 backdrop-blur-lg flex flex-col gap-8 items-center">
<div className="flex-1 space-y-6 w-full">
<img
src="https://cdn.reversed.dev/pictures/20250103_121234.jpg"
alt="Charly Image"
className="w-full h-64 sm:h-96 object-cover rounded-lg shadow-lg"
/>
<p className="text-gray-300 text-base sm:text-lg leading-relaxed">
This is Charly🙏 This lil fella is about{" "}
<span className="italic">13</span> Years old. Very mixed.
</p>
<div className="bg-white/5 p-4 sm:p-6 rounded-lg backdrop-blur-sm">
<h3 className="text-xl font-bold text-gray-100 mb-4 bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">
Charly
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 bg-gray-800 p-4 rounded-lg">
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">🗣️</span>
<span>the guy is a little deaf</span>
</div>
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">🚶</span>
<span>Runs faster than me</span>
</div>
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">🐶</span>
<span>Barks</span>
</div>
<div className="flex items-center gap-3 text-gray-300 hover:text-blue-400 transition-colors">
<span className="text-2xl">🐕</span>
<span>Is a Dog (debatable)</span>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Projects Section */}
<section className="w-full">
<h2 className="text-3xl font-bold text-gray-100 bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Projects
</h2>
<p className="text-gray-300 text-lg mb-4 text-left">
Here are some of my projects. Some are open-source, some are not.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8 text-center">
{projects.map((project, index) => (
<div
key={index}
className="p-6 rounded-xl border border-gray-700 bg-gray-800
transform transition-all duration-300 hover:scale-105 hover:shadow-xl
backdrop-blur-sm hover:bg-opacity-90"
>
{project.image && (
<div className="mb-6 items-center justify-center flex flex-col">
<img
src={project.image}
alt={project.name}
className="h-40 w-40 object-cover rounded-full shadow-lg
outline outline-gray-600 outline-2 hover:outline-blue-500
transition-all duration-300"
/>
</div>
)}
<h3 className="text-xl font-bold mb-3 text-gray-100">
{project.name}
</h3>
<p className=" text-gray-400 text-base mb-4">
{project.description}
</p>
<div className="flex flex-wrap justify-center gap-3 mt-4">
<a
href={
project.link +
"?utm_source=portfolio&utm_medium=referral&ref=space"
}
target="_blank"
className="inline-flex items-center px-4 py-2 bg-gradient-to-r from-blue-500 to-purple-500
text-white rounded-lg font-medium transition-all duration-300
hover:from-blue-600 hover:to-purple-600 hover:shadow-lg hover:scale-105"
>
<span>Visit Project</span>
<svg
className="w-4 h-4 ml-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
{project.open_source && (
<a
href={project.open_source.link}
target="_blank"
className="inline-flex items-center px-4 py-2 bg-gray-800
text-gray-200 rounded-lg font-medium transition-all duration-300
hover:bg-gray-700 hover:shadow-lg hover:scale-105 border border-gray-600"
>
<svg
className="w-4 h-4 mr-2"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
<span>Source Code</span>
</a>
)}
{project.blog_post && (
<a
href={project.blog_post}
className="inline-flex items-center px-4 py-2 bg-gray-800
text-gray-200 rounded-lg font-medium transition-all duration-300
hover:bg-gray-700 hover:shadow-lg hover:scale-105 border border-gray-600"
>
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
/>
</svg>
<span>Read Blog</span>
</a>
)}
</div>
</div>
))}
</div>
</section>
{/* Contact */}
<section className="w-full">
<h2 className="text-3xl font-bold mb-2 text-center text-gray-100 bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Contact
</h2>
<div
className="p-6 rounded-xl border border-gray-700 bg-white/5 backdrop-blur-lg
transform transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<p className="text-gray-300 text-lg text-center">
You can contact me via Email or on Discord. I'm always open for a
chat or a <span className="line-through">coffee</span> tea🫖
</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center mt-6">
<a
href="mailto:paul.w@betternews.app"
className="px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500
text-white rounded-full font-medium transition-all duration-300
hover:from-blue-600 hover:to-purple-600 hover:shadow-lg"
>
Email
</a>
<a
href="https://discord.com/users/456443941169004545"
target="_blank"
className="px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500
text-white rounded-full font-medium transition-all duration-300
hover:from-blue-600 hover:to-purple-600 hover:shadow-lg"
>
Discord
</a>
</div>
</div>
</section>
{/* My "Skills" */}
<section className="w-full">
<h2 className="text-3xl font-bold mb-2 text-center text-gray-100 bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Skills
</h2>
<p className="text-gray-300 text-lg mb-2 text-center">
Skills? What dat
</p>
<div
className="p-6 rounded-xl border border-gray-700 bg-white/5 backdrop-blur-lg
hover:shadow-xl"
>
<ul className="space-y-4 text-center">
{skills.map((skill, index) => (
<li
key={index}
className="flex items-center gap-4 text-gray-300 text-lg transition-all duration-300 hover:text-blue-400"
>
{skill.image && (
<img
src={skill.image}
alt={skill.name}
className="h-12 w-12 object-cover rounded-full shadow-lg"
/>
)}
<span>
{skill.name} - {skill.description}
</span>
</li>
))}
</ul>
</div>
</section>
{/* What's Next Section */}
<section className="w-full">
<h2 className="text-3xl font-bold mb-2 text-center text-gray-100 bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
What's Next?
</h2>
<div
className="p-6 rounded-xl border border-gray-700 bg-white/5 backdrop-blur-lg
transform transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<ul className="space-y-4 text-center">
<li className="text-gray-300 text-lg transition-all duration-300 hover:text-blue-400">
• Learning more ways to build Apps
</li>
<li className="text-gray-300 text-lg transition-all duration-300 hover:text-blue-400">
• Building more fun little open-source Projects
</li>
<li className="text-gray-300 text-lg transition-all duration-300 hover:text-blue-400">
• Working on existing Projects
</li>
</ul>
</div>
</section>
</div>
</div>
);
}
const skills: {
name: string;
description: string;
image?: string;
}[] = [
{
name: "JavaScript",
description:
"I'm a JavaScript Developer with a lot of experience in breaking packages. Js is shit and the learning curve is REALLY easy. Love it",
image: "https://cdn.reversed.dev/pictures/languages/js_logo.png",
},
{
name: "Vue.JS",
description: "Not much experience but its fine",
image: "https://cdn.reversed.dev/pictures/languages/vue.png",
},
{
name: "Node.js",
description: "Node on the server is NOT that bad❗",
image: "https://cdn.reversed.dev/pictures/languages/nodejs.jpg",
},
{
name: "TailwindCSS",
description: "Tailwind my beloved. All screens, crazy responsive design♥",
image: "https://cdn.reversed.dev/pictures/languages/tailwind.png",
},
{
name: "TypeScript",
description:
"Love it, broke it wayy to many times. 1k Compiler errors incoming",
image: "https://cdn.reversed.dev/pictures/languages/ts.png",
},
{
name: "Docker",
description: "As i have found out, deploying isn't that hard...",
image: "https://cdn.reversed.dev/pictures/languages/docker.png",
},
{
name: "HTML",
description: "Yes",
image:
"https://imagedelivery.net/5MYSbk45M80qAwecrlKzdQ/51703b85-ef3f-4d45-fae0-c39d4c733900/preview",
},
];
const projects: {
name: string;
description: string;
image?: string;
open_source: { link: string } | false;
link: string;
blog_post?: string;
}[] = [
{
name: "BetterNews",
description:
"A news aggregator app, custom built. No ads, no tracking, just news.",
image: "https://betternews.app/assets/icon.png",
open_source: false,
link: "https://betternews.app",
},
{
name: "SHSF",
description:
"SHSF is a very simple self hostable API & UI for selfhosting Cloudfunctions on hard ware you already own.",
link: "https://github.com/Space-Banane/shsf",
open_source: { link: "https://github.com/Space-Banane/shsf" },
image: "https://cdn.reversed.dev/pictures/shsf/SHSF.png",
},
{
name: "Lil Cats",
link: "https://cats.reversed.dev",
description: "Funny Cat website. Follow cats around and feed them.",
image: "https://cats.reversed.dev/lil-cats.png",
open_source: { link: "https://github.com/Space-Banane/lil-cats" },
},
{
name: "Whatsapp-Chat-Analyzer",
description:
"A simple Chat Analyzer for WhatsApp Chats. All private, all yours.",
image: "https://cdn.reversed.dev/pictures/wca.png",
open_source: { link: "https://github.com/Space-Banane/whatsapp-stats" },
link: "https://whatstat.reversed.dev",
},
{
name: "Free QrCode Generator",
description:
"A simple QR Code Generator for free. No tracking, no ads, just QR Codes. Just like we love em.",
image: "https://cdn.reversed.dev/pictures/qrcode.jpeg",
open_source: { link: "https://github.com/reversed-dev/qr-code-gen" },
link: "https://qr.reversed.dev",
},
{
// Is Twitch streamer live, "Is-live", https://shsf-api.cottonfieldworkers.shop/api/exec/6/22b5d292-ccf1-473b-8838-4db550d6a1e6
name: "Is-Live",
description:
"Is a Twitch streamer live? Check it out. Simple API, no tracking, no ads.",
image: "https://cdn.reversed.dev/pictures/shsf/SHSF.png",
open_source: { link: "https://github.com/Space-Banane/is-live" },
link: "https://is-live.reversed.dev",
},
];