A new Era
This commit is contained in:
@@ -1,84 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is intended to be a basic starting point for linting in your app.
|
|
||||||
* It relies on recommended configs out of the box for simplicity, but you can
|
|
||||||
* and should modify this configuration to best suit your team's needs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {import('eslint').Linter.Config} */
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
commonjs: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
ignorePatterns: ["!**/.server", "!**/.client"],
|
|
||||||
|
|
||||||
// Base config
|
|
||||||
extends: ["eslint:recommended"],
|
|
||||||
|
|
||||||
overrides: [
|
|
||||||
// React
|
|
||||||
{
|
|
||||||
files: ["**/*.{js,jsx,ts,tsx}"],
|
|
||||||
plugins: ["react", "jsx-a11y"],
|
|
||||||
extends: [
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react/jsx-runtime",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"plugin:jsx-a11y/recommended",
|
|
||||||
],
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
formComponents: ["Form"],
|
|
||||||
linkComponents: [
|
|
||||||
{ name: "Link", linkAttribute: "to" },
|
|
||||||
{ name: "NavLink", linkAttribute: "to" },
|
|
||||||
],
|
|
||||||
"import/resolver": {
|
|
||||||
typescript: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Typescript
|
|
||||||
{
|
|
||||||
files: ["**/*.{ts,tsx}"],
|
|
||||||
plugins: ["@typescript-eslint", "import"],
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
settings: {
|
|
||||||
"import/internal-regex": "^~/",
|
|
||||||
"import/resolver": {
|
|
||||||
node: {
|
|
||||||
extensions: [".ts", ".tsx"],
|
|
||||||
},
|
|
||||||
typescript: {
|
|
||||||
alwaysTryTypes: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
// Node
|
|
||||||
{
|
|
||||||
files: [".eslintrc.cjs"],
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -1,5 +1,24 @@
|
|||||||
node_modules
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
/.cache
|
node_modules
|
||||||
/build
|
dist
|
||||||
.env
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|||||||
73
README.md
Normal file
73
README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## React Compiler
|
||||||
|
|
||||||
|
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
|
||||||
|
// Remove tseslint.configs.recommended and replace with this
|
||||||
|
tseslint.configs.recommendedTypeChecked,
|
||||||
|
// Alternatively, use this for stricter rules
|
||||||
|
tseslint.configs.strictTypeChecked,
|
||||||
|
// Optionally, add this for stylistic rules
|
||||||
|
tseslint.configs.stylisticTypeChecked,
|
||||||
|
|
||||||
|
// Other configs...
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// eslint.config.js
|
||||||
|
import reactX from 'eslint-plugin-react-x'
|
||||||
|
import reactDom from 'eslint-plugin-react-dom'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
// Enable lint rules for React
|
||||||
|
reactX.configs['recommended-typescript'],
|
||||||
|
// Enable lint rules for React DOM
|
||||||
|
reactDom.configs.recommended,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* By default, Remix will handle hydrating your app on the client for you.
|
|
||||||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
|
|
||||||
* For more information, see https://remix.run/file-conventions/entry.client
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { RemixBrowser } from "@remix-run/react";
|
|
||||||
import { startTransition, StrictMode } from "react";
|
|
||||||
import { hydrateRoot } from "react-dom/client";
|
|
||||||
|
|
||||||
startTransition(() => {
|
|
||||||
hydrateRoot(
|
|
||||||
document,
|
|
||||||
<StrictMode>
|
|
||||||
<RemixBrowser />
|
|
||||||
</StrictMode>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
/**
|
|
||||||
* By default, Remix will handle generating the HTTP Response for you.
|
|
||||||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
|
|
||||||
* For more information, see https://remix.run/file-conventions/entry.server
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { PassThrough } from "node:stream";
|
|
||||||
|
|
||||||
import type { AppLoadContext, EntryContext } from "@remix-run/node";
|
|
||||||
import { createReadableStreamFromReadable } from "@remix-run/node";
|
|
||||||
import { RemixServer } from "@remix-run/react";
|
|
||||||
import { isbot } from "isbot";
|
|
||||||
import { renderToPipeableStream } from "react-dom/server";
|
|
||||||
|
|
||||||
const ABORT_DELAY = 5_000;
|
|
||||||
|
|
||||||
export default function handleRequest(
|
|
||||||
request: Request,
|
|
||||||
responseStatusCode: number,
|
|
||||||
responseHeaders: Headers,
|
|
||||||
remixContext: EntryContext,
|
|
||||||
// This is ignored so we can keep it in the template for visibility. Feel
|
|
||||||
// free to delete this parameter in your app if you're not using it!
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
loadContext: AppLoadContext
|
|
||||||
) {
|
|
||||||
return isbot(request.headers.get("user-agent") || "")
|
|
||||||
? handleBotRequest(
|
|
||||||
request,
|
|
||||||
responseStatusCode,
|
|
||||||
responseHeaders,
|
|
||||||
remixContext
|
|
||||||
)
|
|
||||||
: handleBrowserRequest(
|
|
||||||
request,
|
|
||||||
responseStatusCode,
|
|
||||||
responseHeaders,
|
|
||||||
remixContext
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBotRequest(
|
|
||||||
request: Request,
|
|
||||||
responseStatusCode: number,
|
|
||||||
responseHeaders: Headers,
|
|
||||||
remixContext: EntryContext
|
|
||||||
) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let shellRendered = false;
|
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
|
||||||
<RemixServer
|
|
||||||
context={remixContext}
|
|
||||||
url={request.url}
|
|
||||||
abortDelay={ABORT_DELAY}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
onAllReady() {
|
|
||||||
shellRendered = true;
|
|
||||||
const body = new PassThrough();
|
|
||||||
const stream = createReadableStreamFromReadable(body);
|
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
new Response(stream, {
|
|
||||||
headers: responseHeaders,
|
|
||||||
status: responseStatusCode,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
pipe(body);
|
|
||||||
},
|
|
||||||
onShellError(error: unknown) {
|
|
||||||
reject(error);
|
|
||||||
},
|
|
||||||
onError(error: unknown) {
|
|
||||||
responseStatusCode = 500;
|
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
|
||||||
// errors encountered during initial shell rendering since they'll
|
|
||||||
// reject and get logged in handleDocumentRequest.
|
|
||||||
if (shellRendered) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBrowserRequest(
|
|
||||||
request: Request,
|
|
||||||
responseStatusCode: number,
|
|
||||||
responseHeaders: Headers,
|
|
||||||
remixContext: EntryContext
|
|
||||||
) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let shellRendered = false;
|
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
|
||||||
<RemixServer
|
|
||||||
context={remixContext}
|
|
||||||
url={request.url}
|
|
||||||
abortDelay={ABORT_DELAY}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
onShellReady() {
|
|
||||||
shellRendered = true;
|
|
||||||
const body = new PassThrough();
|
|
||||||
const stream = createReadableStreamFromReadable(body);
|
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
new Response(stream, {
|
|
||||||
headers: responseHeaders,
|
|
||||||
status: responseStatusCode,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
pipe(body);
|
|
||||||
},
|
|
||||||
onShellError(error: unknown) {
|
|
||||||
reject(error);
|
|
||||||
},
|
|
||||||
onError(error: unknown) {
|
|
||||||
responseStatusCode = 500;
|
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
|
||||||
// errors encountered during initial shell rendering since they'll
|
|
||||||
// reject and get logged in handleDocumentRequest.
|
|
||||||
if (shellRendered) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
37
app/root.tsx
37
app/root.tsx
@@ -1,37 +0,0 @@
|
|||||||
import {
|
|
||||||
Links,
|
|
||||||
Meta,
|
|
||||||
Outlet,
|
|
||||||
Scripts,
|
|
||||||
ScrollRestoration,
|
|
||||||
} from "@remix-run/react";
|
|
||||||
import type { LinksFunction } from "@remix-run/node";
|
|
||||||
|
|
||||||
import "./tailwind.css";
|
|
||||||
|
|
||||||
export const links: LinksFunction = () => [
|
|
||||||
];
|
|
||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charSet="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link rel="icon" href="https://cdn.reversed.dev/pictures/cat.png" type="image/png" />
|
|
||||||
<Meta />
|
|
||||||
<Links />
|
|
||||||
<script defer src="https://not-a-tracker.reversed.dev/script.js" data-website-id="7d28af45-d984-428e-af79-fb4dc7e91492"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{children}
|
|
||||||
<ScrollRestoration />
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return <Outlet />;
|
|
||||||
}
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default function Index() {
|
|
||||||
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-gray-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.reversed.dev/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">A 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">
|
|
||||||
Hi! I'm <span className="underline font-bold">Paul</span>, a
|
|
||||||
passionate developer from <span className="font-bold">Germany</span> 🇩🇪.
|
|
||||||
<span className="block mt-4">
|
|
||||||
I specialize in JavaScript/TypeScript development, building fun backend systems, experimenting with frontend, and occasionally breaking TypeScript.
|
|
||||||
</span>
|
|
||||||
<span className="block mt-4">
|
|
||||||
I'm a big fan of open-source projects and I hope I can make a small but meaningful impact through my work.
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Projects 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">
|
|
||||||
Projects
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-300 text-lg mb-4 text-center">
|
|
||||||
Here are some of my favorite 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>
|
|
||||||
)}
|
|
||||||
</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">
|
|
||||||
Feel free to reach out to me via Discord or Email. I usually respond quicker to emails.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col sm:flex-row gap-3 justify-center mt-6">
|
|
||||||
<a
|
|
||||||
href="mailto:space@reversed.dev"
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects: {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
image?: string;
|
|
||||||
open_source: { link: string } | false;
|
|
||||||
link: string;
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
name: "BetterNews",
|
|
||||||
description:
|
|
||||||
"A news feed where you submit the news.",
|
|
||||||
image: "https://betternews.app/assets/icon.png",
|
|
||||||
open_source: false,
|
|
||||||
link: "https://betternews.app",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SHSF",
|
|
||||||
description:
|
|
||||||
"Self-hostable \"Cloud Functions\" for your own hardware.",
|
|
||||||
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: "3D Print Card",
|
|
||||||
description: "Showcase Your 3D Prints Beautifully",
|
|
||||||
link:"card.peakprinting.top",
|
|
||||||
image: "https://card.peakprinting.top/icon.png",
|
|
||||||
open_source: { link: "https://github.com/Space-Banane/imsoprintingit" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Lil Cats",
|
|
||||||
link: "https://cats.reversed.dev",
|
|
||||||
description: "Follow cats around and feed them. (Made with GPT-4.1 because I wanted to test it)",
|
|
||||||
image: "https://cats.reversed.dev/lil-cats.png",
|
|
||||||
open_source: { link: "https://github.com/Space-Banane/lil-cats" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Whatsapp-Chat-Analyzer",
|
|
||||||
description:
|
|
||||||
"Analyze your Whatsapp chats with ease. Get insights, stats and more.",
|
|
||||||
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:
|
|
||||||
"I love QR codes that DO NOT NEED A REGISTER!!!! (or ones that expire)",
|
|
||||||
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.reversed.dev/api/exec/6/22b5d292-ccf1-473b-8838-4db550d6a1e6
|
|
||||||
name: "Is-Live",
|
|
||||||
description:
|
|
||||||
"Is a Twitch streamer live? I don't know, find out!",
|
|
||||||
image: "https://cdn.reversed.dev/pictures/islive.png",
|
|
||||||
open_source: { link: "https://github.com/Space-Banane/is-live" },
|
|
||||||
link: "https://is-live.reversed.dev",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
@apply bg-white dark:bg-gray-950;
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,5 +14,6 @@ services:
|
|||||||
npm i -g pnpm &&
|
npm i -g pnpm &&
|
||||||
pnpm install &&
|
pnpm install &&
|
||||||
pnpm build &&
|
pnpm build &&
|
||||||
PORT=6756 pnpm start
|
if ! command -v serve >/dev/null 2>&1; then npm i -g serve; fi &&
|
||||||
|
npx serve -l 6756 dist
|
||||||
"
|
"
|
||||||
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
39
index.html
Normal file
39
index.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="https://cdn.reversed.dev/pictures/cat.png" type="image/png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<title>Space - Developer & Enthusiast</title>
|
||||||
|
<meta name="title" content="Space - Developer & Enthusiast" />
|
||||||
|
<meta name="description" content="Personal portfolio site." />
|
||||||
|
<meta name="author" content="Space" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
|
||||||
|
<!-- Open Graph / Facebook -->
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content="Space - Developer & Enthusiast" />
|
||||||
|
<meta property="og:description" content="Personal portfolio site." />
|
||||||
|
<meta property="og:image" content="https://cdn.reversed.dev/pictures/20250405_120402.png" />
|
||||||
|
|
||||||
|
<!-- Twitter -->
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:title" content="Space - Developer & Enthusiast" />
|
||||||
|
<meta property="twitter:description" content="Personal portfolio site." />
|
||||||
|
<meta property="twitter:image" content="https://cdn.reversed.dev/pictures/20250405_120402.png" />
|
||||||
|
|
||||||
|
<meta name="robots" content="index, follow" />
|
||||||
|
<link rel="canonical" href="https://space.reversed.dev" />
|
||||||
|
|
||||||
|
<script defer src="https://not-a-tracker.reversed.dev/script.js"
|
||||||
|
data-website-id="7d28af45-d984-428e-af79-fb4dc7e91492"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
56
package.json
56
package.json
@@ -1,43 +1,35 @@
|
|||||||
{
|
{
|
||||||
"name": "my-portfolio",
|
"name": "getspaced",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "remix vite:build",
|
"dev": "vite",
|
||||||
"dev": "remix vite:dev",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
|
"lint": "eslint .",
|
||||||
"start": "remix-serve ./build/server/index.js",
|
"preview": "vite preview"
|
||||||
"typecheck": "tsc"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/node": "^2.15.2",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@remix-run/react": "^2.15.2",
|
"react": "^19.2.0",
|
||||||
"@remix-run/serve": "^2.15.2",
|
"react-dom": "^19.2.0",
|
||||||
"isbot": "^4.1.0",
|
"tailwindcss": "^4.1.17"
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@remix-run/dev": "^2.15.2",
|
"@eslint/js": "^9.39.1",
|
||||||
"@types/react": "^18.2.20",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react": "^19.2.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@typescript-eslint/parser": "^6.7.4",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"eslint": "^9.39.1",
|
||||||
"eslint": "^8.38.0",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"eslint-plugin-import": "^2.28.1",
|
"globals": "^16.5.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"typescript": "~5.9.3",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"typescript-eslint": "^8.46.4",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"vite": "npm:rolldown-vite@7.2.5"
|
||||||
"postcss": "^8.5.1",
|
|
||||||
"tailwindcss": "^3.4.17",
|
|
||||||
"typescript": "^5.1.6",
|
|
||||||
"vite": "^5.1.0",
|
|
||||||
"vite-tsconfig-paths": "^4.2.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"overrides": {
|
||||||
"node": ">=20.0.0"
|
"vite": "npm:rolldown-vite@7.2.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
7710
pnpm-lock.yaml
generated
7710
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
|||||||
onlyBuiltDependencies:
|
|
||||||
- esbuild
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
644
src/App.tsx
Normal file
644
src/App.tsx
Normal file
@@ -0,0 +1,644 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
function ProjectCard({ project }: { project: Project }) {
|
||||||
|
return (
|
||||||
|
<div className="group relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/10 hover:border-purple-500/30 transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl hover:shadow-purple-500/10">
|
||||||
|
{project.image && (
|
||||||
|
<div className="mb-6 overflow-hidden rounded-xl bg-black/20 aspect-video flex items-center justify-center relative">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||||
|
<img
|
||||||
|
src={project.image}
|
||||||
|
alt={project.name}
|
||||||
|
className="h-24 w-24 object-cover rounded-full shadow-lg group-hover:scale-110 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
||||||
|
{project.name}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 text-sm flex-grow mb-6 leading-relaxed">
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex gap-3 mt-auto">
|
||||||
|
<a
|
||||||
|
href={project.link + "?utm_source=portfolio&ref=space"}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="flex-1 py-2 px-4 rounded-lg bg-white/10 hover:bg-white/20 text-white text-sm font-medium text-center transition-colors flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
Visit
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
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"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="p-2 rounded-lg bg-white/5 hover:bg-white/10 text-gray-400 hover:text-white transition-colors border border-white/5"
|
||||||
|
title="View Source"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" 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>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [status, setStatus] = useState("");
|
||||||
|
const [borderStatus, setBorderStatus] = useState("border-gray-700");
|
||||||
|
const [glowColor, setGlowColor] = useState("rgba(55, 65, 81, 0.5)");
|
||||||
|
const [statusMessage, setStatusMessage] = useState("Breaking SHSF");
|
||||||
|
const [messageIndex, setMessageIndex] = useState(0);
|
||||||
|
const [displayMessage, setDisplayMessage] = useState("");
|
||||||
|
const [isScrambling, setIsScrambling] = useState(false);
|
||||||
|
const [showOldNames, setShowOldNames] = useState(false);
|
||||||
|
|
||||||
|
const oldUsernames = [
|
||||||
|
"getspaced (current)",
|
||||||
|
"Space-Banane",
|
||||||
|
];
|
||||||
|
|
||||||
|
const rotatingMessages = [
|
||||||
|
"Bricking Esp32's 🧱",
|
||||||
|
"Buy me a White Redbull⁉️",
|
||||||
|
"Testing in production 🧪",
|
||||||
|
"Shipping bug fixes 📦",
|
||||||
|
"Not learning Rust 🦀",
|
||||||
|
"Pushing before Copilot's Review ✅",
|
||||||
|
"Breaking SHSF 🚧",
|
||||||
|
"No debugger attached 🐞",
|
||||||
|
"Asking ChatGPT for help 🤖",
|
||||||
|
"Refactoring for the 10th time 🔄",
|
||||||
|
"Writing documentation 🥹",
|
||||||
|
"Removing Comments 🧹",
|
||||||
|
"Collecting Spotify hours 🎵",
|
||||||
|
];
|
||||||
|
|
||||||
|
const characters =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:',.<>?/`~" +
|
||||||
|
"🧱⁉️🧪📦🦀✅🚧🐞🤖🔄🥹🧹🎵";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === "online") {
|
||||||
|
setBorderStatus("border-green-500");
|
||||||
|
setGlowColor("rgba(34, 197, 94, 0.5)");
|
||||||
|
setStatusMessage("Reachable");
|
||||||
|
} else if (status === "offline") {
|
||||||
|
setBorderStatus("border-gray-500");
|
||||||
|
setGlowColor("rgba(107, 114, 128, 0.5)");
|
||||||
|
setStatusMessage("Probably Away");
|
||||||
|
} else if (status === "dnd") {
|
||||||
|
setBorderStatus("border-red-600");
|
||||||
|
setGlowColor("rgba(220, 38, 38, 0.5)");
|
||||||
|
setStatusMessage("Not Reachable");
|
||||||
|
} else if (status === "idle") {
|
||||||
|
setBorderStatus("border-yellow-500");
|
||||||
|
setGlowColor("rgba(234, 179, 8, 0.5)");
|
||||||
|
setStatusMessage("Doing anything but work");
|
||||||
|
} else {
|
||||||
|
setBorderStatus("border-gray-700");
|
||||||
|
setGlowColor("rgba(55, 65, 81, 0.5)");
|
||||||
|
setStatusMessage("Breaking SHSF");
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(
|
||||||
|
"https://shsf-api.reversed.dev/api/exec/6/c084ec4a-1b20-491e-ab2e-67c5fa8881e6"
|
||||||
|
)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
setStatus(data.status);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
setStatus("offline");
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Scramble effect
|
||||||
|
useEffect(() => {
|
||||||
|
const targetMessage = rotatingMessages[messageIndex];
|
||||||
|
|
||||||
|
if (isScrambling) {
|
||||||
|
let iteration = 0;
|
||||||
|
const scrambleInterval = setInterval(() => {
|
||||||
|
setDisplayMessage(
|
||||||
|
targetMessage
|
||||||
|
.split("")
|
||||||
|
.map((char, index) => {
|
||||||
|
if (index < iteration) {
|
||||||
|
return targetMessage[index];
|
||||||
|
}
|
||||||
|
if (char === " " || /[\u{1F000}-\u{1F9FF}]/u.test(char)) {
|
||||||
|
return char;
|
||||||
|
}
|
||||||
|
return characters[Math.floor(Math.random() * characters.length)];
|
||||||
|
})
|
||||||
|
.join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (iteration >= targetMessage.length) {
|
||||||
|
clearInterval(scrambleInterval);
|
||||||
|
setIsScrambling(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
iteration += 1 / 3;
|
||||||
|
}, 30);
|
||||||
|
|
||||||
|
return () => clearInterval(scrambleInterval);
|
||||||
|
} else {
|
||||||
|
setDisplayMessage(targetMessage);
|
||||||
|
}
|
||||||
|
}, [messageIndex, isScrambling]);
|
||||||
|
|
||||||
|
// Rotating message effect
|
||||||
|
useEffect(() => {
|
||||||
|
const messageInterval = setInterval(() => {
|
||||||
|
setIsScrambling(true);
|
||||||
|
setMessageIndex((prev) => (prev + 1) % rotatingMessages.length);
|
||||||
|
}, 7000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(messageInterval);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen bg-black text-white selection:bg-purple-500/30">
|
||||||
|
{/* Background Grid */}
|
||||||
|
<div className="fixed inset-0 z-0 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 blur-sm"
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
"linear-gradient(#ffffff 1px, transparent 1px), linear-gradient(90deg, #ffffff 1px, transparent 1px)",
|
||||||
|
backgroundSize: "30px 30px",
|
||||||
|
maskImage:
|
||||||
|
"radial-gradient(circle at center, black 40%, transparent 100%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main className="relative z-10 flex flex-col items-center px-6 py-20 mx-auto max-w-5xl gap-24">
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="flex flex-col items-center text-center space-y-8 animate-fade-in-down">
|
||||||
|
<div className="relative group">
|
||||||
|
<div
|
||||||
|
className={`absolute -inset-1 rounded-full blur opacity-75 transition duration-500`}
|
||||||
|
style={{ backgroundColor: glowColor }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`relative w-48 h-48 rounded-full border-4 transition-colors duration-500 overflow-hidden ${borderStatus} bg-black`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://cdn.reversed.dev/pictures/20250405_120402.png"
|
||||||
|
alt="Space"
|
||||||
|
className="w-full h-full object-cover scale-110 transition-transform duration-700 group-hover:scale-125"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rotating Message Bubble (Left Side) */}
|
||||||
|
<div className="absolute -left-4 top-4 -translate-x-full">
|
||||||
|
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-sm border border-purple-500/30 rounded-2xl px-4 py-3 shadow-lg">
|
||||||
|
{/* Arrow pointing to profile */}
|
||||||
|
<div className="absolute right-0 top-1/2 translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/30"></div>
|
||||||
|
<div className="absolute right-0 top-1/2 translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/20"></div>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-200 whitespace-nowrap font-medium font-mono">
|
||||||
|
{displayMessage || rotatingMessages[0]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status Bubble (Right Side) */}
|
||||||
|
<div className="absolute -right-4 top-8 translate-x-full">
|
||||||
|
<div className="relative bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/20 rounded-2xl px-4 py-3 shadow-lg">
|
||||||
|
{/* Arrow pointing to profile */}
|
||||||
|
<div className="absolute left-0 top-1/2 -translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/20"></div>
|
||||||
|
<div className="absolute left-0 top-1/2 -translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/10"></div>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-300 whitespace-nowrap">
|
||||||
|
Currently{" "}
|
||||||
|
<span className="text-white font-semibold">{statusMessage}</span>
|
||||||
|
<br />
|
||||||
|
on Discord
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 max-w-2xl">
|
||||||
|
<h1 className="text-6xl md:text-7xl font-extrabold">
|
||||||
|
Hey, I'm{" "}
|
||||||
|
<span
|
||||||
|
className="relative inline-block text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-purple-500 to-pink-500 cursor-help px-2"
|
||||||
|
style={{ marginLeft: "0.15em", marginRight: "0.15em" }}
|
||||||
|
onMouseEnter={() => setShowOldNames(true)}
|
||||||
|
onMouseLeave={() => setShowOldNames(false)}
|
||||||
|
>
|
||||||
|
Space
|
||||||
|
{showOldNames && (
|
||||||
|
<div className="absolute left-1/2 -translate-x-1/2 top-full mt-4 z-50 animate-fade-in flex justify-center w-full">
|
||||||
|
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-md border border-purple-500/30 rounded-xl px-6 py-4 shadow-2xl min-w-[220px] max-w-xs">
|
||||||
|
<div className="absolute left-1/2 -translate-x-1/2 -top-2 w-0 h-0 border-l-8 border-l-transparent border-r-8 border-r-transparent border-b-8 border-b-purple-500/30"></div>
|
||||||
|
<p className="text-xs text-gray-400 mb-2 font-medium whitespace-nowrap text-center">
|
||||||
|
Also known as:
|
||||||
|
</p>
|
||||||
|
<ul className="flex flex-col items-center gap-1.5">
|
||||||
|
{oldUsernames.map((name, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className="text-sm text-gray-200 font-mono hover:text-purple-400 transition-colors whitespace-nowrap text-center"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-2xl text-gray-400 font-light">
|
||||||
|
A{" "}
|
||||||
|
<span className="line-through decoration-purple-500/50 decoration-2">
|
||||||
|
Self-proclaimed
|
||||||
|
</span>{" "}
|
||||||
|
Developer breaking things to see how they work.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* About Section */}
|
||||||
|
<section className="w-full space-y-8">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
|
||||||
|
About Me
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
A bit about who I am and what I do
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
|
||||||
|
<div className="space-y-4 text-gray-300">
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I'm a Software Developer from Germany, located near Frankfurt.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
My passion lies in coding, exploring limits, and occasionally
|
||||||
|
reinventing the wheel just for the fun of it. While I love
|
||||||
|
open-source, I'm not afraid to build custom solutions when
|
||||||
|
existing ones don't fit.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I actively contribute to open-source projects, fixing the little
|
||||||
|
things that make a big difference.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* AI-Assisted Coding Section */}
|
||||||
|
<section className="w-full space-y-8">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
|
||||||
|
AI-Assisted Coding
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
How i use AI.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-3xl mx-auto space-y-6">
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
|
||||||
|
<div className="space-y-4 text-gray-300">
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I write most of my code myself, especially when it comes to logic, backend architecture,
|
||||||
|
authentication systems, database communication, and project setup. These core aspects are
|
||||||
|
where I believe hands-on coding is essential for quality and understanding.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
However, for UI development, I love using{" "}
|
||||||
|
<span className="text-purple-400 font-semibold">Claude Sonnet 4.5</span> to
|
||||||
|
help speed up the process and explore different design approaches. It's particularly helpful for
|
||||||
|
styling, layout, and creating polished user interfaces.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I also embrace tools like{" "}
|
||||||
|
<span className="text-blue-400 font-semibold">GitHub Copilot's autocompletion</span>,
|
||||||
|
which enhances my productivity by suggesting code snippets and reducing repetitive typing.
|
||||||
|
It's like having a coding assistant that understands context.
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 p-4 rounded-xl bg-white/5 border border-white/10">
|
||||||
|
<p className="text-sm text-gray-400 italic text-center">
|
||||||
|
"Ai wont replace shit, it'll either help us or kill us"
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Hardware Mods & Builds Section */}
|
||||||
|
<section className="w-full space-y-8">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-orange-400 to-red-500">
|
||||||
|
Hardware Stuff
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
The lion is concerned with a LED connected to 12V
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-orange-500/10 to-red-500/5 backdrop-blur-sm border border-orange-500/20">
|
||||||
|
<div className="space-y-4 text-gray-300">
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
When I'm not breaking my code, you'll find me breaking{" "}
|
||||||
|
<span className="text-orange-400 font-semibold">Arduinos</span> and{" "}
|
||||||
|
<span className="text-orange-400 font-semibold">ESP32s</span>. I love interacting via Software with self-made hardware.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I've built my fair share of little sensor gadgets and placed them around my house. All are managed using{" "}
|
||||||
|
<span className="text-blue-400 font-semibold">ESPHome</span> via{" "}
|
||||||
|
<span className="text-blue-400 font-semibold">Home Assistant</span>. Which means, no cloud dependencies and full control over my smart home setup.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I love using Arduinos because they're stupidly easy to build with & the bar to entry is extremely low with a stupid amount of tutorials,
|
||||||
|
but the possibilities are endless. First it was the blinking LEDs, now it's servo control via bluetooth across the room.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 3D Printing Section */}
|
||||||
|
<section className="w-full space-y-8">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-blue-500">
|
||||||
|
3D Printing
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
I turn Plastic on a Spool into Plastic in a Shape.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-cyan-500/10 to-blue-500/5 backdrop-blur-sm border border-cyan-500/20">
|
||||||
|
<div className="space-y-4 text-gray-300">
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I run a modded Creality Ender 3 V1 with a{" "}
|
||||||
|
<span className="text-cyan-400 font-semibold">BLTouch</span> for auto bed leveling,
|
||||||
|
all managed remotely through <span className="text-cyan-400 font-semibold">OctoPrint</span>.
|
||||||
|
I slice my prints using <span className="text-cyan-400 font-semibold">OrcaSlicer</span> because Cura misses a few options.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I mostly print replacement parts for stuff around the house and cool shit I find on{" "}
|
||||||
|
<span className="text-cyan-400 font-semibold">Printables</span>. For custom designs, I use{" "}
|
||||||
|
<span className="text-cyan-400 font-semibold">Fusion 360</span> to model my own parts when I need something specific.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I'm not a fan of Bamboo Lab. Their choices, pricing, and quality just don't. <br />
|
||||||
|
One day, I'd love to own a <span className="text-green-400 font-semibold">Creality K2 Pro</span>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Goals Section */}
|
||||||
|
<section className="w-full space-y-8">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
|
||||||
|
My Goals
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
This is the stuff i want to learn.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 rounded-lg bg-blue-500/20 text-3xl">
|
||||||
|
🚀
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">
|
||||||
|
Not Just Semi-Fullstack
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">
|
||||||
|
I want to work more with Serverless Architectures and Cloud Services to build scalable applications.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-purple-500/10 to-pink-500/5 backdrop-blur-sm border border-purple-500/20">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 rounded-lg bg-purple-500/20 text-3xl">
|
||||||
|
💻
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">
|
||||||
|
Contribute More to Open Source
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">
|
||||||
|
I want to commit more to Open-Source Projects. That's it...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-green-500/10 to-blue-500/5 backdrop-blur-sm border border-green-500/20">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 rounded-lg bg-green-500/20 text-3xl">
|
||||||
|
⚡
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">
|
||||||
|
Expand on Existing Projects
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">
|
||||||
|
I want to make SHSF more stable and usable.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-yellow-500/10 to-orange-500/5 backdrop-blur-sm border border-yellow-500/20">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 rounded-lg bg-yellow-500/20 text-3xl">
|
||||||
|
🦀
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">
|
||||||
|
Explore the Unknown
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">
|
||||||
|
Rust?👀
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Projects Section */}
|
||||||
|
<section className="w-full space-y-12">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-pink-500 to-orange-400">
|
||||||
|
Favourite Projects
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
Personal Faviorite? Uhhhhh....
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{projects.map((project, index) => (
|
||||||
|
<ProjectCard key={index} project={project} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Contact Section */}
|
||||||
|
<section className="w-full max-w-2xl text-center space-y-4 pb-8">
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-blue-500 to-purple-500">
|
||||||
|
Let's Connect
|
||||||
|
</h2>
|
||||||
|
<div className="p-8 rounded-3xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 border border-blue-500/20 backdrop-blur-md">
|
||||||
|
<p className="text-gray-300 mb-8 text-lg">
|
||||||
|
Got anything on your mind? Shoot me a DM or Email.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a
|
||||||
|
href="mailto:space@reversed.dev"
|
||||||
|
className="px-8 py-3 rounded-full bg-gradient-to-r from-white to-gray-100 text-black font-bold hover:from-gray-100 hover:to-white transition-all shadow-lg shadow-white/20 hover:shadow-white/30"
|
||||||
|
>
|
||||||
|
Send Email
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://discord.com/users/456443941169004545"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="px-8 py-3 rounded-full bg-gradient-to-r from-[#5865F2] to-[#4752C4] text-white font-bold hover:from-[#4752C4] hover:to-[#5865F2] transition-all shadow-lg shadow-[#5865F2]/30 hover:shadow-[#5865F2]/50"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="w-full border-t border-white/10 pt-4">
|
||||||
|
<div className="text-center space-y-3">
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<img
|
||||||
|
src="http://shsf.reversed.dev/SHSF%20SMALL%20TRANSPARENT.png"
|
||||||
|
alt="SHSF Logo"
|
||||||
|
className="w-6 h-6"
|
||||||
|
/>
|
||||||
|
<p className="text-gray-400 text-sm">
|
||||||
|
Partly powered by{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/Space-Banane/shsf"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-purple-400 hover:text-purple-300 transition-colors"
|
||||||
|
>
|
||||||
|
SHSF
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-400 text-sm">Love from Space ❤️</p>
|
||||||
|
<p className="text-gray-500 text-xs">
|
||||||
|
Designed with Copilot &{" "}
|
||||||
|
<a
|
||||||
|
href="https://gradienty.codes/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-purple-400 hover:text-purple-300 transition-colors underline"
|
||||||
|
>
|
||||||
|
gradienty.codes
|
||||||
|
</a>
|
||||||
|
, Tailwind and React
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Project {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
image?: string;
|
||||||
|
link: string;
|
||||||
|
open_source: false | { link: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
const projects: Project[] = [
|
||||||
|
{
|
||||||
|
name: "BetterNews",
|
||||||
|
description: "A news feed where you submit the news.",
|
||||||
|
image: "https://betternews.app/assets/icon.png",
|
||||||
|
open_source: false,
|
||||||
|
link: "https://betternews.app",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SHSF",
|
||||||
|
description: 'Self-hostable "Cloud Functions" for your own hardware.',
|
||||||
|
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: "3D Print Card",
|
||||||
|
description: "Showcase Your 3D Prints Beautifully",
|
||||||
|
link: "https://card.peakprinting.top",
|
||||||
|
image: "https://card.peakprinting.top/icon.png",
|
||||||
|
open_source: { link: "https://github.com/Space-Banane/imsoprintingit" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Whatsapp-Chat-Analyzer",
|
||||||
|
description:
|
||||||
|
"Analyze your Whatsapp chats with ease. Get insights, stats and more.",
|
||||||
|
image: "https://cdn.reversed.dev/pictures/wca.png",
|
||||||
|
open_source: { link: "https://github.com/Space-Banane/whatsapp-stats" },
|
||||||
|
link: "https://whatstat.reversed.dev",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "QrCode Generator",
|
||||||
|
description: "Vue.JS based QR Code Generator with a bit of customization.",
|
||||||
|
image: "https://cdn.reversed.dev/pictures/qrcode.jpeg",
|
||||||
|
open_source: { link: "https://github.com/reversed-dev/qr-code-gen" },
|
||||||
|
link: "https://qr.reversed.dev",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default App;
|
||||||
1
src/index.css
Normal file
1
src/index.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import type { Config } from "tailwindcss";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
fontFamily: {
|
|
||||||
sans: [
|
|
||||||
"Inter",
|
|
||||||
"ui-sans-serif",
|
|
||||||
"system-ui",
|
|
||||||
"sans-serif",
|
|
||||||
"Apple Color Emoji",
|
|
||||||
"Segoe UI Emoji",
|
|
||||||
"Segoe UI Symbol",
|
|
||||||
"Noto Color Emoji",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
} satisfies Config;
|
|
||||||
28
tsconfig.app.json
Normal file
28
tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -1,32 +1,7 @@
|
|||||||
{
|
{
|
||||||
"include": [
|
"files": [],
|
||||||
"**/*.ts",
|
"references": [
|
||||||
"**/*.tsx",
|
{ "path": "./tsconfig.app.json" },
|
||||||
"**/.server/**/*.ts",
|
{ "path": "./tsconfig.node.json" }
|
||||||
"**/.server/**/*.tsx",
|
]
|
||||||
"**/.client/**/*.ts",
|
|
||||||
"**/.client/**/*.tsx"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
||||||
"types": ["@remix-run/node", "vite/client"],
|
|
||||||
"isolatedModules": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"target": "ES2022",
|
|
||||||
"strict": true,
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./app/*"]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Vite takes care of building everything, not tsc.
|
|
||||||
"noEmit": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
26
tsconfig.node.json
Normal file
26
tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -1,24 +1,8 @@
|
|||||||
import { vitePlugin as remix } from "@remix-run/dev";
|
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import react from "@vitejs/plugin-react";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
declare module "@remix-run/node" {
|
|
||||||
interface Future {
|
|
||||||
v3_singleFetch: true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [react(), tailwindcss()],
|
||||||
remix({
|
|
||||||
future: {
|
|
||||||
v3_fetcherPersist: true,
|
|
||||||
v3_relativeSplatPath: true,
|
|
||||||
v3_throwAbortReason: true,
|
|
||||||
v3_singleFetch: true,
|
|
||||||
v3_lazyRouteDiscovery: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
tsconfigPaths(),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user