Compare commits

..

2 Commits

Author SHA256 Message Date
9f8b81fd0c Merge branch 'master' of https://git.fingeri.ng/raped.cc/kittens.rip_bio 2026-05-18 22:27:54 +02:00
db92ad2f88 Initial commit 2026-05-18 22:26:10 +02:00
34 changed files with 2158 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/dist
/node_modules
package-lock.json

BIN
assets/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
assets/capi.mp3 Normal file

Binary file not shown.

BIN
assets/moan.mp3 Normal file

Binary file not shown.

18
index.html Normal file
View File

@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/media/bg.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="depoliticized. — personal identity & portfolio" />
<meta name="theme-color" content="#1A1A1A" />
<title>depoliticized.</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;700&family=Space+Grotesk:wght@300;400;500;600;700&family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

28
package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "depoliticized-portfolio",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"gsap": "^3.12.5",
"lenis": "^1.1.13",
"lucide-react": "^0.460.0"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.15",
"typescript": "^5.6.3",
"vite": "^6.0.1"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

13
pub-pgp.txt Normal file
View File

@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZzigpRYJKwYBBAHaRw8BAQdAt3o7m1YIEwDC+wStOTX4FRbkjEFTJ3KiSEC9
YLcUk+20HWV4Y3VzM3MgPGNvbnRhY3RAc3RlYWxlci53dGY+iJkEExYKAEEWIQRv
xuv7mSWfs1bTXy2ARacLZTIVdwUCZzigpQIbAwUJBaN1CwULCQgHAgIiAgYVCgkI
CwIEFgIDAQIeBwIXgAAKCRCARacLZTIVd5PjAP49C0bBm5Utku41cNmWJ4H+JcJP
FJyO6VCkPc6RlSpn5gEA7mGcOMkAym9ASFShJ1/o7ZZGSBhnFJ2HQx7H0lDEzAq4
OARnOKClEgorBgEEAZdVAQUBAQdADUszFmROJl5SKOWrEuth432zT4AXav+lqS2q
fPaSfjsDAQgHiH4EGBYKACYWIQRvxuv7mSWfs1bTXy2ARacLZTIVdwUCZzigpQIb
DAUJBaN1CwAKCRCARacLZTIVd8yWAP4nq3mnf/QKmfAouqTjV6hxaPDjAH3x9axk
hGSQOOnhGgD/Zfoc+wr9YA7CfNIcZMsIalsH4RzKwAXtjyIJRea4OAA=
=LFH8
-----END PGP PUBLIC KEY BLOCK-----

BIN
public/media/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
public/media/capi.mp3 Normal file

Binary file not shown.

BIN
public/media/moan.mp3 Normal file

Binary file not shown.

13
public/pub-pgp.txt Normal file
View File

@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZzigpRYJKwYBBAHaRw8BAQdAt3o7m1YIEwDC+wStOTX4FRbkjEFTJ3KiSEC9
YLcUk+20HWV4Y3VzM3MgPGNvbnRhY3RAc3RlYWxlci53dGY+iJkEExYKAEEWIQRv
xuv7mSWfs1bTXy2ARacLZTIVdwUCZzigpQIbAwUJBaN1CwULCQgHAgIiAgYVCgkI
CwIEFgIDAQIeBwIXgAAKCRCARacLZTIVd5PjAP49C0bBm5Utku41cNmWJ4H+JcJP
FJyO6VCkPc6RlSpn5gEA7mGcOMkAym9ASFShJ1/o7ZZGSBhnFJ2HQx7H0lDEzAq4
OARnOKClEgorBgEEAZdVAQUBAQdADUszFmROJl5SKOWrEuth432zT4AXav+lqS2q
fPaSfjsDAQgHiH4EGBYKACYWIQRvxuv7mSWfs1bTXy2ARacLZTIVdwUCZzigpQIb
DAUJBaN1CwAKCRCARacLZTIVd8yWAP4nq3mnf/QKmfAouqTjV6hxaPDjAH3x9axk
hGSQOOnhGgD/Zfoc+wr9YA7CfNIcZMsIalsH4RzKwAXtjyIJRea4OAA=
=LFH8
-----END PGP PUBLIC KEY BLOCK-----

111
src/App.tsx Normal file
View File

@@ -0,0 +1,111 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import Lenis from 'lenis'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import LoadingScreen from './components/LoadingScreen'
import Navigation from './components/Navigation'
import HeroSection from './components/HeroSection'
import AboutSection from './components/AboutSection'
import InterestsSection from './components/InterestsSection'
import ProjectsSection from './components/ProjectsSection'
import ExperienceSection from './components/ExperienceSection'
import TopicsSection from './components/TopicsSection'
import ContactSection from './components/ContactSection'
import Footer from './components/Footer'
import FloatingParticles from './components/FloatingParticles'
import ScrollProgress from './components/ScrollProgress'
import NoiseOverlay from './components/NoiseOverlay'
import { useReducedMotion } from './hooks/useReducedMotion'
gsap.registerPlugin(ScrollTrigger)
export default function App() {
const [loaded, setLoaded] = useState(false)
const [showContent, setShowContent] = useState(false)
const lenisRef = useRef<Lenis | null>(null)
const mainRef = useRef<HTMLElement>(null)
const capiAudioRef = useRef<HTMLAudioElement | null>(null)
const reducedMotion = useReducedMotion()
useEffect(() => {
capiAudioRef.current = new Audio('./media/capi.mp3')
capiAudioRef.current.preload = 'auto'
capiAudioRef.current.loop = true
capiAudioRef.current.volume = 0.25
}, [])
useEffect(() => {
if (!loaded) return
const lenis = new Lenis({
duration: 1.2,
easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smoothWheel: true,
})
lenisRef.current = lenis
lenis.on('scroll', ScrollTrigger.update)
const rafCallback = (time: number) => {
lenis.raf(time * 1000)
}
gsap.ticker.add(rafCallback)
gsap.ticker.lagSmoothing(0)
return () => {
gsap.ticker.remove(rafCallback)
lenis.destroy()
}
}, [loaded])
const handleLoadComplete = useCallback(() => {
setLoaded(true)
// Small delay before showing content for transition
setTimeout(() => {
setShowContent(true)
}, 100)
}, [])
useEffect(() => {
if (showContent) {
// Start ambient capi.mp3 loop
capiAudioRef.current?.play().catch(() => {})
}
}, [showContent])
useEffect(() => {
if (showContent && mainRef.current && !reducedMotion) {
gsap.fromTo(
mainRef.current,
{ opacity: 0, y: 40 },
{ opacity: 1, y: 0, duration: 1.2, ease: 'expo.out' }
)
}
}, [showContent, reducedMotion])
return (
<>
<LoadingScreen onLoadComplete={handleLoadComplete} />
{showContent && (
<>
<ScrollProgress />
<NoiseOverlay />
<FloatingParticles />
<Navigation />
<main ref={mainRef} className="relative" style={{ opacity: reducedMotion ? 1 : 0 }}>
<HeroSection />
<AboutSection />
<InterestsSection />
<ProjectsSection />
<ExperienceSection />
<TopicsSection />
<ContactSection />
<Footer />
</main>
</>
)}
</>
)
}

View File

@@ -0,0 +1,128 @@
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { MapPin, BookOpen, User } from "lucide-react";
import { useReducedMotion } from "../hooks/useReducedMotion";
gsap.registerPlugin(ScrollTrigger);
export default function AboutSection() {
const sectionRef = useRef<HTMLElement>(null);
const reducedMotion = useReducedMotion();
useEffect(() => {
if (reducedMotion || !sectionRef.current) return;
const ctx = gsap.context(() => {
gsap.fromTo(
".about-reveal",
{ y: 50, opacity: 0 },
{
y: 0,
opacity: 1,
duration: 1,
stagger: 0.15,
ease: "expo.out",
scrollTrigger: {
trigger: sectionRef.current,
start: "top 70%",
toggleActions: "play none none none",
},
},
);
}, sectionRef);
return () => ctx.revert();
}, [reducedMotion]);
return (
<section
id="about"
ref={sectionRef}
className="relative py-32 md:py-40 px-6"
>
<div className="max-w-6xl mx-auto">
{/* Section label */}
<div className="about-reveal flex items-center gap-4 mb-16">
<span className="text-pink-brand font-mono text-xs tracking-[0.3em] uppercase">
001
</span>
<div className="flex-1 h-px bg-pink-brand/20" />
<span className="text-grey-500 font-display text-xs tracking-[0.2em] uppercase">
Identity
</span>
</div>
{/* Main heading */}
<h2 className="about-reveal font-display text-4xl md:text-6xl lg:text-7xl font-light text-grey-900 mb-16 leading-tight">
A young adult from
<br />
<span className="text-gradient">Europe</span>
</h2>
{/* Content grid */}
<div className="grid grid-cols-1 md:grid-cols-12 gap-8 md:gap-12">
{/* Left column - main text */}
<div className="md:col-span-7 space-y-8">
<p className="about-reveal text-grey-600 text-lg md:text-xl leading-relaxed font-body">
I am a curious mind navigating the intersection of technology,
psychology, and philosophy. My work spans from low-level systems
to sophisticated web platforms, always driven by a desire to
understand how things work beneath the surface.
</p>
<p className="about-reveal text-grey-500 text-base leading-relaxed font-body">
Reading is a core part of who I am - it shapes how I think about
problems and approach solutions. I believe in building things that
are both technically sound and thoughtfully designed.
</p>
</div>
{/* Right column - details */}
<div className="md:col-span-5 space-y-6">
<div className="about-reveal group p-6 rounded-2xl bg-skin-100/50 border border-pink-brand/5 hover:border-pink-brand/15 transition-all duration-300 hover:shadow-lg hover:shadow-pink-brand/5">
<div className="flex items-start gap-4">
<div className="p-3 rounded-xl bg-pink-brand/10 text-pink-brand group-hover:bg-pink-brand group-hover:text-white transition-all duration-300">
<MapPin className="w-5 h-5" />
</div>
<div>
<h3 className="font-display font-medium text-grey-900 mb-1">
Location
</h3>
<p className="text-grey-500 text-sm font-body">Europe</p>
</div>
</div>
</div>
<div className="about-reveal group p-6 rounded-2xl bg-skin-100/50 border border-pink-brand/5 hover:border-pink-brand/15 transition-all duration-300 hover:shadow-lg hover:shadow-pink-brand/5">
<div className="flex items-start gap-4">
<div className="p-3 rounded-xl bg-pink-brand/10 text-pink-brand group-hover:bg-pink-brand group-hover:text-white transition-all duration-300">
<User className="w-5 h-5" />
</div>
<div>
<h3 className="font-display font-medium text-grey-900 mb-1">
Age
</h3>
<p className="text-grey-500 text-sm font-body">Young adult</p>
</div>
</div>
</div>
<div className="about-reveal group p-6 rounded-2xl bg-skin-100/50 border border-pink-brand/5 hover:border-pink-brand/15 transition-all duration-300 hover:shadow-lg hover:shadow-pink-brand/5">
<div className="flex items-start gap-4">
<div className="p-3 rounded-xl bg-pink-brand/10 text-pink-brand group-hover:bg-pink-brand group-hover:text-white transition-all duration-300">
<BookOpen className="w-5 h-5" />
</div>
<div>
<h3 className="font-display font-medium text-grey-900 mb-1">
Passion
</h3>
<p className="text-grey-500 text-sm font-body">Avid reader</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,258 @@
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import {
MessageSquare,
ExternalLink,
Copy,
Check,
Key,
type LucideIcon,
} from "lucide-react";
import { useReducedMotion } from "../hooks/useReducedMotion";
gsap.registerPlugin(ScrollTrigger);
interface ContactItem {
label: string;
value: string;
href?: string;
isLink?: boolean;
unavailable?: boolean;
icon?: LucideIcon;
}
interface ContactGroup {
category: string;
items: ContactItem[];
}
const contacts: ContactGroup[] = [
{
category: "Messaging",
items: [
{
label: "Simplex",
value: "Open chat",
href: "https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2F3_XOiEnk2Ebup7DqgOwGM5kXlIZX-uJo%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAPJz5X-WHhJk_y1MLRxy7NTfspD-vKF0pmmi_GAJpExY%253D%26q%3Dc%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion",
isLink: true,
},
{
label: "Discord",
value:
"@sysrand [1408202642588438589] / @forgecadrape [1452113626365169958]",
},
{ label: "Telegram", value: "@forgecadrape (main) / @xorededx" },
{ label: "XMPP", value: "sw@anoxinon.me (main) / me0w@xmpp.jp" },
{
label: "Session",
value:
"056834db96cedde6012da9d5402c683c0eb260ed866da145a8f86e7f329bf77222",
},
{
label: "Tox",
value:
"5622A2D2218AF235E39E356792270413BBE7CAEEAA531A20EEF11BF2FD10576E5050F0A55970",
},
{ label: "Potato Chat", value: "@depoliticized" },
{
label: "Element",
value: "Open profile",
href: "https://matrix.to/#/@depoliticized:tchncs.de",
isLink: true,
},
{ label: "Briar", value: "temporary unavailable", unavailable: true },
],
},
{
category: "Email",
items: [
{
label: "Primary",
value: "contact@stealer.wtf (temporary unavailable)",
unavailable: true,
},
],
},
{
category: "Git",
items: [
{
label: "serpent256",
value: "View profile",
href: "https://git.fingeri.ng/serpent256",
isLink: true,
},
{
label: "raped.cc",
value: "View profile (main)",
href: "https://git.fingeri.ng/raped.cc",
isLink: true,
},
{
label: "whiskers",
value: "View org",
href: "https://git.fingeri.ng/whiskers",
isLink: true,
},
],
},
{
category: "PGP",
items: [
{
label: "Public Key",
value: "Download /pub-pgp.txt",
href: "./pub-pgp.txt",
isLink: true,
icon: Key,
},
],
},
];
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Fallback
}
};
return (
<button
onClick={handleCopy}
className="p-1.5 rounded-md text-grey-400 hover:text-pink-brand hover:bg-pink-brand/10 transition-all"
aria-label="Copy to clipboard"
>
{copied ? (
<Check className="w-3.5 h-3.5" />
) : (
<Copy className="w-3.5 h-3.5" />
)}
</button>
);
}
export default function ContactSection() {
const sectionRef = useRef<HTMLElement>(null);
const reducedMotion = useReducedMotion();
useEffect(() => {
if (reducedMotion || !sectionRef.current) return;
const ctx = gsap.context(() => {
gsap.fromTo(
".contact-reveal",
{ y: 40, opacity: 0 },
{
y: 0,
opacity: 1,
duration: 0.8,
stagger: 0.1,
ease: "expo.out",
scrollTrigger: {
trigger: sectionRef.current,
start: "top 70%",
toggleActions: "play none none none",
},
},
);
}, sectionRef);
return () => ctx.revert();
}, [reducedMotion]);
return (
<section
id="contact"
ref={sectionRef}
className="relative py-32 md:py-40 px-6 overflow-hidden"
>
{!reducedMotion && (
<div className="absolute bottom-0 left-1/2 w-[800px] h-[400px] -translate-x-1/2 translate-y-1/2 rounded-full bg-pink-brand/3 blur-[120px] pointer-events-none" />
)}
<div className="max-w-6xl mx-auto relative z-10">
{/* Section label */}
<div className="contact-reveal flex items-center gap-4 mb-16">
<span className="text-pink-brand font-mono text-xs tracking-[0.3em] uppercase">
006
</span>
<div className="flex-1 h-px bg-pink-brand/20" />
<span className="text-grey-500 font-display text-xs tracking-[0.2em] uppercase">
Contact
</span>
</div>
{/* Header */}
<div className="contact-reveal mb-16">
<h2 className="font-display text-4xl md:text-6xl lg:text-7xl font-light text-grey-900 mb-4">
Get in <span className="text-gradient">touch</span>
</h2>
<p className="text-grey-500 text-lg font-body max-w-xl">
Reach out through any of these channels. I prefer encrypted
messaging when possible.
</p>
</div>
{/* Contact grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{contacts.map((group) => (
<div key={group.category} className="contact-reveal">
<div className="flex items-center gap-2 mb-4">
<MessageSquare className="w-4 h-4 text-pink-brand" />
<h3 className="font-display text-sm font-medium text-grey-900 uppercase tracking-widest">
{group.category}
</h3>
</div>
<div className="space-y-3">
{group.items.map((item) => (
<div
key={item.label + item.value}
className="group flex items-center justify-between p-4 rounded-xl bg-skin-100/40 border border-pink-brand/5 hover:border-pink-brand/15 hover:bg-skin-100/70 transition-all duration-300"
>
<div className="flex-1 min-w-0">
<span className="text-xs font-mono text-grey-400 block mb-0.5">
{item.label}
</span>
{item.isLink && item.href ? (
<a
href={item.href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-sm text-grey-700 hover:text-pink-brand transition-colors font-body"
>
{item.icon && <item.icon className="w-3.5 h-3.5" />}
{item.value}
<ExternalLink className="w-3 h-3" />
</a>
) : item.unavailable ? (
<span className="text-sm italic text-red-400 font-body">
{item.value}
</span>
) : (
<span className="text-sm text-grey-700 font-body break-all">
{item.value}
</span>
)}
</div>
{!item.isLink && !item.unavailable && (
<CopyButton text={item.value} />
)}
</div>
))}
</div>
</div>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,135 @@
import { useEffect, useRef } from 'react'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { Code2, Server, Shield, Cpu, GitBranch, Bot, Cloud, Layers } from 'lucide-react'
import { useReducedMotion } from '../hooks/useReducedMotion'
gsap.registerPlugin(ScrollTrigger)
const skills = [
{ icon: Code2, label: 'Languages', items: 'Rust, V, D, Go, TypeScript, Python, PHP, Elixir' },
{ icon: Layers, label: 'Frontend', items: 'Astro, Svelte 5, lightweight & modern UIs' },
{ icon: Server, label: 'Backend & APIs', items: 'REST API design, complex infrastructure' },
{ icon: Cpu, label: 'Low Level', items: 'Systems programming, kernel research' },
{ icon: Shield, label: 'Security Research', items: 'Evasion, exfiltration, cryptography' },
{ icon: GitBranch, label: 'DevOps', items: 'Self-hosting, Docker, monorepo architecture' },
{ icon: Bot, label: 'Automation', items: 'Telegram / Discord bots & scripts' },
{ icon: Cloud, label: 'Networks', items: 'Secure communications, protocol design' },
]
const earlyProjects = [
{ name: 'Rose-Stealer_old', url: 'https://github.com/0xRose/Rose-Stealer_old', desc: 'Professional & efficient credential stealer written in python' },
{ name: 'Rose-RAT', url: 'https://github.com/0xRose/Rose-RAT', desc: 'Remote Administration Toolkit Extension to Rose-Stealer with web-host and client controller' },
{ name: 'Rose-Stealer', url: 'https://github.com/0xRose/Rose-Stealer', desc: 'Slightly refined & more modern version of Rosev1' },
{ name: 'Rose-Obf', url: 'https://github.com/gumbobrot/Rose-Obf', desc: 'Rose Python obfuscator & encryptor' },
{ name: 'RoseGuardian', url: 'https://github.com/gumbobrot/RoseGuardian', desc: 'Prior experimental version of Rose obf' },
{ name: 'PyAnalyzer', url: 'https://github.com/gumbobrot/PyAnalyzer', desc: 'Python script utilizing pycdc and pyinstxtractor to decompile pyinstaller packed executables' },
{ name: 'Knight-RAT', url: 'https://github.com/gumbobrot/Knight-RAT', desc: 'Discord-bot managed Remote Access Trojan' },
]
export default function ExperienceSection() {
const sectionRef = useRef<HTMLElement>(null)
const reducedMotion = useReducedMotion()
useEffect(() => {
if (reducedMotion || !sectionRef.current) return
const ctx = gsap.context(() => {
gsap.fromTo(
'.exp-reveal',
{ y: 40, opacity: 0 },
{
y: 0,
opacity: 1,
duration: 0.8,
stagger: 0.08,
ease: 'expo.out',
scrollTrigger: {
trigger: sectionRef.current,
start: 'top 70%',
toggleActions: 'play none none none',
},
}
)
}, sectionRef)
return () => ctx.revert()
}, [reducedMotion])
return (
<section
id="experience"
ref={sectionRef}
className="relative py-32 md:py-40 px-6 overflow-hidden"
>
{!reducedMotion && (
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-pink-brand/3 blur-[100px] pointer-events-none translate-x-1/2 -translate-y-1/2" />
)}
<div className="max-w-6xl mx-auto relative z-10">
{/* Section label */}
<div className="exp-reveal flex items-center gap-4 mb-16">
<span className="text-pink-brand font-mono text-xs tracking-[0.3em] uppercase">
004
</span>
<div className="flex-1 h-px bg-pink-brand/20" />
<span className="text-grey-500 font-display text-xs tracking-[0.2em] uppercase">
Experience
</span>
</div>
{/* Skills grid */}
<h3 className="exp-reveal font-display text-2xl md:text-3xl font-light text-grey-900 mb-10">
Capabilities
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-24">
{skills.map((skill) => (
<div
key={skill.label}
className="exp-reveal group p-5 rounded-2xl bg-skin-100/40 border border-pink-brand/5 hover:border-pink-brand/15 hover:bg-skin-100/70 transition-all duration-300"
>
<skill.icon className="w-5 h-5 text-pink-brand mb-3 group-hover:scale-110 transition-transform" />
<h4 className="font-display text-sm font-medium text-grey-900 mb-1">{skill.label}</h4>
<p className="text-grey-500 text-xs leading-relaxed font-body">{skill.items}</p>
</div>
))}
</div>
{/* Early history */}
<h3 className="exp-reveal font-display text-2xl md:text-3xl font-light text-grey-900 mb-4">
Early History
</h3>
<p className="exp-reveal text-grey-500 text-base font-body mb-10 max-w-2xl">
Projects from 2+ years ago that shaped my foundation. These represent my first
serious forays into software development and security research.
</p>
<div className="space-y-3">
{earlyProjects.map((project) => (
<a
key={project.name}
href={project.url}
target="_blank"
rel="noopener noreferrer"
className="exp-reveal group flex items-center justify-between p-4 rounded-xl bg-skin-100/30 border border-pink-brand/5 hover:border-pink-brand/15 hover:bg-skin-100/60 transition-all duration-300"
>
<div className="flex items-center gap-4">
<GitBranch className="w-4 h-4 text-grey-400 group-hover:text-pink-brand transition-colors" />
<div>
<span className="font-display text-sm font-medium text-grey-900 group-hover:text-pink-brand transition-colors">
{project.name}
</span>
<p className="text-grey-500 text-xs font-body hidden sm:block">{project.desc}</p>
</div>
</div>
<span className="text-xs font-mono text-grey-400 group-hover:text-pink-brand transition-colors">
github
</span>
</a>
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,94 @@
import { useEffect, useRef } from 'react'
import { useReducedMotion } from '../hooks/useReducedMotion'
interface Particle {
x: number
y: number
size: number
speedX: number
speedY: number
opacity: number
color: string
}
export default function FloatingParticles() {
const canvasRef = useRef<HTMLCanvasElement>(null)
const reducedMotion = useReducedMotion()
useEffect(() => {
if (reducedMotion) return
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
let animationId: number
let particles: Particle[] = []
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
resize()
window.addEventListener('resize', resize)
const colors = ['rgba(255, 45, 122, 0.15)', 'rgba(255, 107, 157, 0.12)', 'rgba(255, 182, 193, 0.1)']
const createParticles = () => {
particles = []
const count = Math.min(Math.floor(window.innerWidth / 15), 80)
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 3 + 1,
speedX: (Math.random() - 0.5) * 0.3,
speedY: (Math.random() - 0.5) * 0.3,
opacity: Math.random() * 0.5 + 0.1,
color: colors[Math.floor(Math.random() * colors.length)],
})
}
}
createParticles()
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
particles.forEach((p) => {
p.x += p.speedX
p.y += p.speedY
if (p.x < 0) p.x = canvas.width
if (p.x > canvas.width) p.x = 0
if (p.y < 0) p.y = canvas.height
if (p.y > canvas.height) p.y = 0
ctx.beginPath()
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2)
ctx.fillStyle = p.color
ctx.fill()
})
animationId = requestAnimationFrame(animate)
}
animate()
return () => {
cancelAnimationFrame(animationId)
window.removeEventListener('resize', resize)
}
}, [reducedMotion])
if (reducedMotion) return null
return (
<canvas
ref={canvasRef}
className="fixed inset-0 z-0 pointer-events-none"
style={{ opacity: 0.6 }}
/>
)
}

23
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,23 @@
import { Heart } from 'lucide-react'
export default function Footer() {
const year = new Date().getFullYear()
return (
<footer className="relative py-16 px-6 border-t border-pink-brand/5">
<div className="max-w-6xl mx-auto flex flex-col items-center justify-center text-center gap-4">
<p className="font-mono text-sm text-grey-400 tracking-[0.3em]">
depoliticized.
</p>
<p className="flex items-center gap-1.5 text-xs text-grey-500 font-body">
Built with <Heart className="w-3 h-3 text-pink-brand fill-pink-brand" /> in Europe
</p>
<p className="font-mono text-xs text-grey-400">
{year}
</p>
</div>
</footer>
)
}

View File

@@ -0,0 +1,101 @@
import { useEffect, useRef, useState } from 'react'
import { gsap } from 'gsap'
import { ChevronDown } from 'lucide-react'
import { useReducedMotion } from '../hooks/useReducedMotion'
export default function HeroSection() {
const sectionRef = useRef<HTMLElement>(null)
const titleRef = useRef<HTMLHeadingElement>(null)
const subtitleRef = useRef<HTMLParagraphElement>(null)
const reducedMotion = useReducedMotion()
const [subtitleText, setSubtitleText] = useState('')
const fullSubtitle = 'identity · research · code'
useEffect(() => {
if (reducedMotion) {
setSubtitleText(fullSubtitle)
return
}
// Title entrance animation
if (titleRef.current) {
gsap.fromTo(
titleRef.current,
{ y: 60, opacity: 0, scale: 0.95 },
{ y: 0, opacity: 1, scale: 1, duration: 1.4, ease: 'expo.out', delay: 0.3 }
)
}
// Typing effect for subtitle
let index = 0
const interval = setInterval(() => {
if (index <= fullSubtitle.length) {
setSubtitleText(fullSubtitle.slice(0, index))
index++
} else {
clearInterval(interval)
}
}, 80)
return () => clearInterval(interval)
}, [reducedMotion])
const handleScrollDown = () => {
const about = document.querySelector('#about')
if (about) about.scrollIntoView({ behavior: 'smooth' })
}
return (
<section
ref={sectionRef}
className="relative min-h-screen flex flex-col items-center justify-center overflow-hidden"
>
{/* Ambient shapes */}
{!reducedMotion && (
<>
<div className="absolute top-1/5 left-1/5 w-[500px] h-[500px] rounded-full bg-pink-brand/5 blur-[100px] ambient-shape-1 pointer-events-none" />
<div className="absolute bottom-1/4 right-1/5 w-[400px] h-[400px] rounded-full bg-pink-soft/5 blur-[80px] ambient-shape-2 pointer-events-none" />
<div className="absolute top-1/3 right-1/4 w-2 h-2 rounded-full bg-pink-brand/30 ambient-shape-3 pointer-events-none" />
<div className="absolute bottom-1/3 left-1/3 w-3 h-3 rounded-full bg-pink-soft/20 ambient-shape-1 pointer-events-none" style={{ animationDelay: '-5s' }} />
</>
)}
{/* Main content */}
<div className="relative z-10 text-center px-6">
<h1
ref={titleRef}
className="font-mono font-light text-grey-900 select-none text-[clamp(1.75rem,7vw,10rem)] tracking-[0.05em] md:tracking-[0.15em] leading-[1.1]"
>
depoliticized.
</h1>
<p
ref={subtitleRef}
className="mt-6 font-display text-grey-500 text-sm md:text-base tracking-[0.3em] uppercase typing-cursor"
>
{subtitleText}
</p>
<div className="mt-12 flex justify-center">
<div className="w-px h-16 bg-pink-brand/20 relative overflow-hidden">
{!reducedMotion && (
<div className="absolute top-0 left-0 w-full h-1/2 bg-pink-brand/60 animate-pulse-slow" />
)}
</div>
</div>
</div>
{/* Scroll indicator */}
<button
onClick={handleScrollDown}
className="absolute bottom-8 left-1/2 -translate-x-1/2 text-grey-500 hover:text-pink-brand transition-colors animate-float"
aria-label="Scroll down"
>
<ChevronDown className="w-6 h-6" />
</button>
{/* Edge gradient fade */}
<div className="absolute bottom-0 left-0 right-0 h-32 bg-skin-50" style={{ maskImage: 'linear-gradient(to bottom, transparent, black)' }} />
</section>
)
}

View File

@@ -0,0 +1,160 @@
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { Music, Brain, Sparkles } from "lucide-react";
import { useReducedMotion } from "../hooks/useReducedMotion";
gsap.registerPlugin(ScrollTrigger);
const musicGenres = [
"Nu-Metal",
"Alt-Metal",
"Scenecore",
"Nightcore",
"Russian Hardtekk",
"EDM",
"90's Music",
];
export default function InterestsSection() {
const sectionRef = useRef<HTMLElement>(null);
const reducedMotion = useReducedMotion();
useEffect(() => {
if (reducedMotion || !sectionRef.current) return;
const ctx = gsap.context(() => {
gsap.fromTo(
".interest-reveal",
{ y: 40, opacity: 0 },
{
y: 0,
opacity: 1,
duration: 0.8,
stagger: 0.1,
ease: "expo.out",
scrollTrigger: {
trigger: sectionRef.current,
start: "top 70%",
toggleActions: "play none none none",
},
},
);
gsap.fromTo(
".genre-tag",
{ scale: 0.8, opacity: 0 },
{
scale: 1,
opacity: 1,
duration: 0.6,
stagger: 0.06,
ease: "back.out(1.7)",
scrollTrigger: {
trigger: sectionRef.current,
start: "top 60%",
toggleActions: "play none none none",
},
},
);
}, sectionRef);
return () => ctx.revert();
}, [reducedMotion]);
return (
<section
id="interests"
ref={sectionRef}
className="relative py-32 md:py-40 px-6 overflow-hidden"
>
{/* Background ambient */}
{!reducedMotion && (
<div className="absolute top-1/2 left-0 w-[600px] h-[600px] -translate-y-1/2 -translate-x-1/2 rounded-full bg-pink-brand/3 blur-[120px] pointer-events-none" />
)}
<div className="max-w-6xl mx-auto relative z-10">
{/* Section label */}
<div className="interest-reveal flex items-center gap-4 mb-16">
<span className="text-pink-brand font-mono text-xs tracking-[0.3em] uppercase">
002
</span>
<div className="flex-1 h-px bg-pink-brand/20" />
<span className="text-grey-500 font-display text-xs tracking-[0.2em] uppercase">
Interests
</span>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20">
{/* Music */}
<div>
<div className="interest-reveal flex items-center gap-3 mb-8">
<div className="p-3 rounded-xl bg-pink-brand/10 text-pink-brand">
<Music className="w-6 h-6" />
</div>
<h3 className="font-display text-2xl md:text-3xl font-light text-grey-900">
Music
</h3>
</div>
<p className="interest-reveal text-grey-500 text-base leading-relaxed font-body mb-8">
Music is essential to my rhythm. From heavy riffs to high-speed
electronic beats, these are the sounds that fuel my focus and
creative energy.
</p>
<div className="flex flex-wrap gap-3">
{musicGenres.map((genre) => (
<span
key={genre}
className="genre-tag px-4 py-2 rounded-full text-sm font-display text-grey-700 bg-skin-100 border border-pink-brand/10 hover:border-pink-brand/30 hover:bg-pink-brand/5 hover:text-pink-brand transition-all duration-300 cursor-default"
>
{genre}
</span>
))}
</div>
</div>
{/* Mind */}
<div>
<div className="interest-reveal flex items-center gap-3 mb-8">
<div className="p-3 rounded-xl bg-pink-brand/10 text-pink-brand">
<Brain className="w-6 h-6" />
</div>
<h3 className="font-display text-2xl md:text-3xl font-light text-grey-900">
The Mind
</h3>
</div>
<p className="interest-reveal text-grey-500 text-base leading-relaxed font-body mb-8">
Beyond code, I am deeply fascinated by how humans think, feel, and
perceive reality. Psychology and philosophy provide the frameworks
I use to understand complex systems - both technical and human.
</p>
<div className="interest-reveal grid grid-cols-2 gap-4">
<div className="p-5 rounded-2xl bg-skin-100/50 border border-pink-brand/5 hover:border-pink-brand/15 transition-all duration-300 group">
<Sparkles className="w-5 h-5 text-pink-brand mb-3 group-hover:scale-110 transition-transform" />
<h4 className="font-display font-medium text-grey-900 text-sm mb-1">
Psychology
</h4>
<p className="text-grey-500 text-xs font-body">
Cognitive patterns & behavior
</p>
</div>
<div className="p-5 rounded-2xl bg-skin-100/50 border border-pink-brand/5 hover:border-pink-brand/15 transition-all duration-300 group">
<Sparkles className="w-5 h-5 text-pink-brand mb-3 group-hover:scale-110 transition-transform" />
<h4 className="font-display font-medium text-grey-900 text-sm mb-1">
Philosophy
</h4>
<p className="text-grey-500 text-xs font-body">
Ethics, logic & existence
</p>
</div>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,187 @@
import { useState, useRef, useEffect, useCallback } from 'react'
import { gsap } from 'gsap'
import { Volume2, VolumeX, AlertTriangle, Loader2 } from 'lucide-react'
import { useReducedMotion } from '../hooks/useReducedMotion'
interface LoadingScreenProps {
onLoadComplete: () => void
}
export default function LoadingScreen({ onLoadComplete }: LoadingScreenProps) {
const [accepted, setAccepted] = useState(false)
const [loading, setLoading] = useState(false)
const [progress, setProgress] = useState(0)
const [audioEnabled, setAudioEnabled] = useState(true)
const [exiting, setExiting] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
const audioRef = useRef<HTMLAudioElement | null>(null)
const reducedMotion = useReducedMotion()
useEffect(() => {
audioRef.current = new Audio('./media/moan.mp3')
audioRef.current.preload = 'auto'
}, [])
useEffect(() => {
if (!containerRef.current || reducedMotion) return
gsap.fromTo(
containerRef.current.querySelectorAll('.load-item'),
{ y: 30, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.8, stagger: 0.12, ease: 'expo.out', delay: 0.2 }
)
}, [reducedMotion])
const handleLoad = useCallback(() => {
if (!accepted || loading) return
setLoading(true)
// Play moan audio during loading
if (audioEnabled && audioRef.current) {
audioRef.current.volume = 0.6
audioRef.current.play().catch(() => {
// Audio blocked by browser policy, continue anyway
})
}
// Simulate loading progress
const startTime = Date.now()
const duration = 2500 // 2.5s minimum load time
const updateProgress = () => {
const elapsed = Date.now() - startTime
const p = Math.min((elapsed / duration) * 100, 100)
setProgress(p)
if (p < 100) {
requestAnimationFrame(updateProgress)
} else {
// Finish loading
setTimeout(() => {
// Stop moan.mp3 before transitioning
if (audioRef.current) {
audioRef.current.pause()
audioRef.current.currentTime = 0
}
setExiting(true)
setTimeout(() => {
onLoadComplete()
}, 800)
}, 300)
}
}
requestAnimationFrame(updateProgress)
}, [accepted, loading, audioEnabled, onLoadComplete])
return (
<div
ref={containerRef}
className={`fixed inset-0 z-[100] flex flex-col items-center justify-center bg-grey-900 transition-all duration-700 ${
exiting ? 'opacity-0 scale-105' : 'opacity-100 scale-100'
}`}
style={{ pointerEvents: exiting ? 'none' : 'auto' }}
>
{/* Ambient background shapes */}
{!reducedMotion && (
<>
<div className="absolute top-1/4 left-1/4 w-64 h-64 rounded-full bg-pink-brand/5 blur-3xl ambient-shape-1" />
<div className="absolute bottom-1/3 right-1/4 w-96 h-96 rounded-full bg-pink-soft/5 blur-3xl ambient-shape-2" />
</>
)}
<div className="relative z-10 flex flex-col items-center max-w-md w-full px-6">
{/* Banner image */}
<div className="load-item mb-8 relative">
<div className="overflow-hidden rounded-lg shadow-2xl shadow-pink-brand/10">
<img
src="./media/bg.png"
alt="Banner"
className="w-48 h-auto object-cover"
draggable={false}
/>
</div>
{!reducedMotion && (
<div className="absolute -inset-1 rounded-lg bg-pink-brand/20 blur-xl -z-10 animate-pulse-slow" />
)}
</div>
{/* Load text */}
<h1 className="load-item text-4xl md:text-5xl font-mono font-light text-skin-100 tracking-widest mb-2">
Load~
</h1>
<p className="load-item text-grey-500 text-sm mb-8 font-body">
depoliticized.
</p>
{/* Warning */}
<div className="load-item w-full mb-6">
<div className="flex items-start gap-3 p-4 rounded-xl bg-yellow-500/10 border border-yellow-500/20">
<AlertTriangle className="w-5 h-5 text-yellow-400 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<label className="flex items-start gap-3 cursor-pointer group">
<input
type="checkbox"
checked={accepted}
onChange={(e) => setAccepted(e.target.checked)}
className="mt-1 w-4 h-4 rounded border-yellow-500/30 bg-transparent accent-pink-brand focus:ring-pink-brand/50 focus:ring-2"
/>
<span className="text-yellow-200/80 text-xs leading-relaxed font-body">
I acknowledge that the creator is not responsible for any content shown on this page. All links, features, contacts, projects, etc. are entirely for research and personal educational purposes, not meant to be deployed or actually harm anyone.
</span>
</label>
</div>
</div>
</div>
{/* Audio toggle */}
<div className="load-item flex items-center gap-2 mb-6">
<button
onClick={() => setAudioEnabled(!audioEnabled)}
className="flex items-center gap-2 text-xs text-grey-500 hover:text-pink-soft transition-colors"
>
{audioEnabled ? <Volume2 className="w-4 h-4" /> : <VolumeX className="w-4 h-4" />}
<span>{audioEnabled ? 'Sound on' : 'Sound off'}</span>
</button>
</div>
{/* Load button */}
<button
onClick={handleLoad}
disabled={!accepted || loading}
className={`load-item group relative px-12 py-4 rounded-full font-display font-medium text-sm tracking-wider uppercase transition-all duration-300 overflow-hidden ${
accepted && !loading
? 'bg-pink-brand text-white hover:bg-pink-bright hover:shadow-lg hover:shadow-pink-brand/30 hover:scale-105'
: 'bg-grey-700 text-grey-500 cursor-not-allowed'
}`}
>
{loading ? (
<span className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
Loading...
</span>
) : (
<span className="relative z-10">Load</span>
)}
{!loading && accepted && !reducedMotion && (
<span className="absolute inset-0 bg-pink-bright translate-y-full group-hover:translate-y-0 transition-transform duration-300 ease-out" />
)}
</button>
{/* Progress bar */}
{loading && (
<div className="load-item w-full mt-8">
<div className="h-1 w-full bg-grey-700 rounded-full overflow-hidden">
<div
className="h-full bg-pink-brand rounded-full transition-all duration-100 ease-out"
style={{ width: `${progress}%` }}
/>
</div>
<p className="text-center text-grey-500 text-xs mt-2 font-mono">
{Math.round(progress)}%
</p>
</div>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,133 @@
import { useState, useEffect } from 'react'
import { Menu, X } from 'lucide-react'
const navItems = [
{ label: 'About', href: '#about' },
{ label: 'Interests', href: '#interests' },
{ label: 'Projects', href: '#projects' },
{ label: 'Experience', href: '#experience' },
{ label: 'Topics', href: '#topics' },
{ label: 'Contact', href: '#contact' },
]
export default function Navigation() {
const [visible, setVisible] = useState(false)
const [mobileOpen, setMobileOpen] = useState(false)
const [activeSection, setActiveSection] = useState('')
useEffect(() => {
const handleScroll = () => {
setVisible(window.scrollY > window.innerHeight * 0.6)
}
window.addEventListener('scroll', handleScroll, { passive: true })
return () => window.removeEventListener('scroll', handleScroll)
}, [])
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveSection(entry.target.id)
}
})
},
{ rootMargin: '-40% 0px -40% 0px' }
)
navItems.forEach((item) => {
const el = document.querySelector(item.href)
if (el) observer.observe(el)
})
return () => observer.disconnect()
}, [])
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
e.preventDefault()
const el = document.querySelector(href)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
setMobileOpen(false)
}
}
return (
<>
<nav
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
visible ? 'translate-y-0 opacity-100' : '-translate-y-full opacity-0'
}`}
>
<div className="glass">
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<a
href="#"
onClick={(e) => {
e.preventDefault()
window.scrollTo({ top: 0, behavior: 'smooth' })
}}
className="font-mono text-sm text-grey-900 hover:text-pink-brand transition-colors tracking-widest"
>
depoliticized.
</a>
{/* Desktop nav */}
<div className="hidden md:flex items-center gap-8">
{navItems.map((item) => (
<a
key={item.href}
href={item.href}
onClick={(e) => handleClick(e, item.href)}
className={`text-xs font-display uppercase tracking-widest transition-colors relative ${
activeSection === item.href.slice(1)
? 'text-pink-brand'
: 'text-grey-600 hover:text-grey-900'
}`}
>
{item.label}
{activeSection === item.href.slice(1) && (
<span className="absolute -bottom-1 left-0 right-0 h-px bg-pink-brand" />
)}
</a>
))}
</div>
{/* Mobile toggle */}
<button
className="md:hidden text-grey-900"
onClick={() => setMobileOpen(!mobileOpen)}
aria-label="Toggle menu"
>
{mobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
</div>
{/* Mobile menu */}
<div
className={`md:hidden glass overflow-hidden transition-all duration-300 ${
mobileOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="px-6 py-4 flex flex-col gap-4">
{navItems.map((item) => (
<a
key={item.href}
href={item.href}
onClick={(e) => handleClick(e, item.href)}
className={`text-sm font-display uppercase tracking-widest transition-colors ${
activeSection === item.href.slice(1)
? 'text-pink-brand'
: 'text-grey-600 hover:text-grey-900'
}`}
>
{item.label}
</a>
))}
</div>
</div>
</nav>
</>
)
}

View File

@@ -0,0 +1,17 @@
import { useReducedMotion } from '../hooks/useReducedMotion'
export default function NoiseOverlay() {
const reducedMotion = useReducedMotion()
if (reducedMotion) return null
return (
<div
className="fixed inset-0 z-[55] pointer-events-none opacity-[0.015]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")`,
backgroundRepeat: 'repeat',
backgroundSize: '128px 128px',
}}
/>
)
}

View File

@@ -0,0 +1,243 @@
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import {
Activity,
Rocket,
CheckCircle2,
ExternalLink,
Terminal,
Globe,
Lock,
Database,
} from "lucide-react";
import { useReducedMotion } from "../hooks/useReducedMotion";
gsap.registerPlugin(ScrollTrigger);
const activeProjects = [
{
title: "Seraph C2",
description:
"Full fledged, interactive, post-exploitation C2 system with AI-support, E2EE, evasion, cross-platform featuring and large scaled exfiltration features.",
icon: Terminal,
tags: ["Security Research", "AI", "E2EE"],
status: "LIVE",
},
{
title: "AnonPay (tocador.app-style)",
description:
"Payment gateway that allows paying in hundreds of different cryptocurrencies whilst still receiving only one by utilizing multiple swap aggregators and joining different, rotating wallet pools.",
icon: Globe,
tags: ["Crypto", "Finance", "Payments"],
status: "LIVE",
},
{
title: "kippenkartell.cc",
description:
"Dockerized Premium SaaS marketplace platform with a clean monorepo architecture: SvelteKit frontend, Rust REST API, MySQL database.",
icon: Database,
tags: ["SaaS", "SvelteKit", "Rust"],
status: "N/A",
},
{
title: "spying.su",
description:
"Discord, Social Media & Breach Data Aggregation and Web Intelligence Platform.",
icon: Lock,
tags: ["OSINT", "Data", "Intelligence"],
status: "N/A",
},
];
const plannedProjects = [
{
title: "webforum",
description:
"Suitable & simple breachforum's & WPD-style webforum for anonymous media submission, role assignment systems, localized restriction control & community/topic merging, additionally private chats & E2EE secured chats.",
},
{
title: "porn page",
description:
"A webforum/archive & user-control system for manageable, quick, fast & secure access/uploading to/of pornography media.",
},
{
title: "vpn-protocol",
description:
"Low-level interface VPN protocol featuring extremely tiny binary & lightweight byte transmitting using NDISAPI (curve25519 -> serpent(threefish512)).",
},
];
const completedProjects = [
{
title: "Cryptography Library",
description:
"A threefish512 (customized padding & improved security) implementation in D based off a russian barebones cryptographic algorithm & a ported shamir's secret sharing implementation in python with a bridger written in C.",
link: "https://git.fingeri.ng/whiskers/cryptography",
},
];
export default function ProjectsSection() {
const sectionRef = useRef<HTMLElement>(null);
const reducedMotion = useReducedMotion();
useEffect(() => {
if (reducedMotion || !sectionRef.current) return;
const ctx = gsap.context(() => {
gsap.fromTo(
".project-reveal",
{ y: 50, opacity: 0 },
{
y: 0,
opacity: 1,
duration: 0.9,
stagger: 0.12,
ease: "expo.out",
scrollTrigger: {
trigger: sectionRef.current,
start: "top 70%",
toggleActions: "play none none none",
},
},
);
}, sectionRef);
return () => ctx.revert();
}, [reducedMotion]);
return (
<section
id="projects"
ref={sectionRef}
className="relative py-32 md:py-40 px-6"
>
<div className="max-w-6xl mx-auto">
{/* Section label */}
<div className="project-reveal flex items-center gap-4 mb-16">
<span className="text-pink-brand font-mono text-xs tracking-[0.3em] uppercase">
003
</span>
<div className="flex-1 h-px bg-pink-brand/20" />
<span className="text-grey-500 font-display text-xs tracking-[0.2em] uppercase">
Projects
</span>
</div>
{/* Active Projects */}
<div className="mb-24">
<div className="project-reveal flex items-center gap-3 mb-10">
<Activity className="w-5 h-5 text-pink-brand" />
<h3 className="font-display text-xl md:text-2xl font-medium text-grey-900">
Active
</h3>
<span className="px-2 py-0.5 rounded-full bg-pink-brand/10 text-pink-brand text-xs font-mono">
{activeProjects.length}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{activeProjects.map((project) => (
<div
key={project.title}
className="project-reveal group p-6 md:p-8 rounded-2xl bg-skin-100/40 border border-pink-brand/5 hover:border-pink-brand/20 hover:bg-skin-100/80 transition-all duration-500 hover:shadow-xl hover:shadow-pink-brand/5"
>
<div className="flex items-start justify-between mb-4">
<div className="p-3 rounded-xl bg-pink-brand/10 text-pink-brand group-hover:bg-pink-brand group-hover:text-white transition-all duration-300">
<project.icon className="w-5 h-5" />
</div>
<span className="flex items-center gap-1.5 text-xs font-mono text-pink-brand">
<span className="w-1.5 h-1.5 rounded-full bg-pink-brand animate-pulse" />
{project.status}
</span>
</div>
<h4 className="font-display text-lg font-medium text-grey-900 mb-3 group-hover:text-pink-brand transition-colors">
{project.title}
</h4>
<p className="text-grey-500 text-sm leading-relaxed font-body mb-4">
{project.description}
</p>
<div className="flex flex-wrap gap-2">
{project.tags.map((tag) => (
<span
key={tag}
className="px-2 py-1 rounded-md text-xs font-mono text-grey-600 bg-skin-200/50"
>
{tag}
</span>
))}
</div>
</div>
))}
</div>
</div>
{/* Planned Projects */}
<div className="mb-24">
<div className="project-reveal flex items-center gap-3 mb-10">
<Rocket className="w-5 h-5 text-pink-soft" />
<h3 className="font-display text-xl md:text-2xl font-medium text-grey-900">
Planned
</h3>
<span className="px-2 py-0.5 rounded-full bg-pink-soft/10 text-pink-soft text-xs font-mono">
{plannedProjects.length}
</span>
</div>
<div className="space-y-4">
{plannedProjects.map((project, i) => (
<div
key={project.title}
className="project-reveal group flex gap-6 p-6 rounded-2xl bg-skin-100/30 border border-pink-brand/5 hover:border-pink-brand/15 hover:bg-skin-100/60 transition-all duration-300"
>
<span className="font-mono text-2xl text-pink-brand/30 group-hover:text-pink-brand/60 transition-colors">
{String(i + 1).padStart(2, "0")}
</span>
<div>
<h4 className="font-display text-base font-medium text-grey-900 mb-2 group-hover:text-pink-brand transition-colors">
{project.title}
</h4>
<p className="text-grey-500 text-sm leading-relaxed font-body">
{project.description}
</p>
</div>
</div>
))}
</div>
</div>
{/* Completed Projects */}
<div>
<div className="project-reveal flex items-center gap-3 mb-10">
<CheckCircle2 className="w-5 h-5 text-grey-500" />
<h3 className="font-display text-xl md:text-2xl font-medium text-grey-900">
Completed
</h3>
</div>
<div className="grid grid-cols-1 gap-4">
{completedProjects.map((project) => (
<a
key={project.title}
href={project.link}
target="_blank"
rel="noopener noreferrer"
className="project-reveal group flex items-start justify-between gap-4 p-6 rounded-2xl bg-skin-100/30 border border-pink-brand/5 hover:border-pink-brand/20 hover:bg-skin-100/60 transition-all duration-300"
>
<div>
<h4 className="font-display text-base font-medium text-grey-900 mb-2 group-hover:text-pink-brand transition-colors">
{project.title}
</h4>
<p className="text-grey-500 text-sm leading-relaxed font-body">
{project.description}
</p>
</div>
<ExternalLink className="w-5 h-5 text-grey-400 group-hover:text-pink-brand flex-shrink-0 transition-colors" />
</a>
))}
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react'
import { useReducedMotion } from '../hooks/useReducedMotion'
export default function ScrollProgress() {
const [progress, setProgress] = useState(0)
const reducedMotion = useReducedMotion()
useEffect(() => {
if (reducedMotion) return
const handleScroll = () => {
const scrollTop = window.scrollY
const docHeight = document.documentElement.scrollHeight - window.innerHeight
const scrollPercent = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0
setProgress(scrollPercent)
}
window.addEventListener('scroll', handleScroll, { passive: true })
return () => window.removeEventListener('scroll', handleScroll)
}, [reducedMotion])
if (reducedMotion) return null
return (
<div className="fixed top-0 left-0 right-0 z-[60] h-[2px] bg-transparent">
<div
className="h-full bg-pink-brand transition-all duration-100 ease-out"
style={{ width: `${progress}%` }}
/>
</div>
)
}

View File

@@ -0,0 +1,137 @@
import { useEffect, useRef } from 'react'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { Telescope, ArrowUpRight } from 'lucide-react'
import { useReducedMotion } from '../hooks/useReducedMotion'
gsap.registerPlugin(ScrollTrigger)
const topics = [
{
title: 'OpenClaw / MCP Servers',
description: 'Exploring modular control plane architectures and automated orchestration systems.',
},
{
title: 'ML / AI Training',
description: 'Deep diving into model training pipelines, fine-tuning strategies, and deployment patterns.',
},
{
title: 'LLM Definition & Background',
description: 'Understanding the theoretical foundations and architectural evolution of large language models.',
},
{
title: 'Low Level & Exploit Development',
description: 'Memory corruption, binary exploitation, and vulnerability research at the systems level.',
},
{
title: 'Game Security Research',
description: 'External / internal cheat development & kernel level anti-cheat exploitation research.',
},
{
title: 'Database Engineering',
description: 'Efficiency, management & scaling strategies for high-throughput data systems.',
},
{
title: 'Stability & Concurrency',
description: 'Improved async handling, fault tolerance, and concurrent system design patterns.',
},
]
export default function TopicsSection() {
const sectionRef = useRef<HTMLElement>(null)
const reducedMotion = useReducedMotion()
useEffect(() => {
if (reducedMotion || !sectionRef.current) return
const ctx = gsap.context(() => {
gsap.fromTo(
'.topic-reveal',
{ y: 30, opacity: 0 },
{
y: 0,
opacity: 1,
duration: 0.7,
stagger: 0.1,
ease: 'expo.out',
scrollTrigger: {
trigger: sectionRef.current,
start: 'top 70%',
toggleActions: 'play none none none',
},
}
)
}, sectionRef)
return () => ctx.revert()
}, [reducedMotion])
return (
<section
id="topics"
ref={sectionRef}
className="relative py-32 md:py-40 px-6"
>
<div className="max-w-6xl mx-auto">
{/* Section label */}
<div className="topic-reveal flex items-center gap-4 mb-16">
<span className="text-pink-brand font-mono text-xs tracking-[0.3em] uppercase">
005
</span>
<div className="flex-1 h-px bg-pink-brand/20" />
<span className="text-grey-500 font-display text-xs tracking-[0.2em] uppercase">
Research
</span>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
{/* Left sticky heading */}
<div className="lg:col-span-4">
<div className="lg:sticky lg:top-32">
<div className="topic-reveal flex items-center gap-3 mb-6">
<div className="p-3 rounded-xl bg-pink-brand/10 text-pink-brand">
<Telescope className="w-6 h-6" />
</div>
</div>
<h2 className="topic-reveal font-display text-3xl md:text-4xl lg:text-5xl font-light text-grey-900 leading-tight mb-6">
Topics to<br />
<span className="text-gradient">explore</span>
</h2>
<p className="topic-reveal text-grey-500 text-base font-body leading-relaxed">
A living list of research directions and technical frontiers
I am actively looking into or plan to investigate.
</p>
</div>
</div>
{/* Right topic list */}
<div className="lg:col-span-8 space-y-4">
{topics.map((topic, i) => (
<div
key={topic.title}
className="topic-reveal group p-6 rounded-2xl bg-skin-100/40 border border-pink-brand/5 hover:border-pink-brand/20 hover:bg-skin-100/70 transition-all duration-300 cursor-default"
>
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-4">
<span className="font-mono text-sm text-pink-brand/40 group-hover:text-pink-brand/70 transition-colors mt-1">
{String(i + 1).padStart(2, '0')}
</span>
<div>
<h4 className="font-display text-base font-medium text-grey-900 mb-1 group-hover:text-pink-brand transition-colors">
{topic.title}
</h4>
<p className="text-grey-500 text-sm font-body leading-relaxed">
{topic.description}
</p>
</div>
</div>
<ArrowUpRight className="w-4 h-4 text-grey-300 group-hover:text-pink-brand flex-shrink-0 mt-1 transition-colors" />
</div>
</div>
))}
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,16 @@
import { useState, useEffect } from 'react'
export function useReducedMotion(): boolean {
const [reducedMotion, setReducedMotion] = useState(false)
useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
setReducedMotion(mq.matches)
const handler = (e: MediaQueryListEvent) => setReducedMotion(e.matches)
mq.addEventListener('change', handler)
return () => mq.removeEventListener('change', handler)
}, [])
return reducedMotion
}

View File

@@ -0,0 +1,53 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import { useReducedMotion } from './useReducedMotion'
interface UseTypingEffectOptions {
text: string
speed?: number
delay?: number
onComplete?: () => void
}
export function useTypingEffect({ text, speed = 50, delay = 0, onComplete }: UseTypingEffectOptions) {
const [displayed, setDisplayed] = useState('')
const [started, setStarted] = useState(false)
const [done, setDone] = useState(false)
const reducedMotion = useReducedMotion()
const indexRef = useRef(0)
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const start = useCallback(() => {
if (started) return
setStarted(true)
}, [started])
useEffect(() => {
if (!started) return
if (reducedMotion) {
setDisplayed(text)
setDone(true)
onComplete?.()
return
}
const type = () => {
if (indexRef.current < text.length) {
setDisplayed(text.slice(0, indexRef.current + 1))
indexRef.current++
timeoutRef.current = setTimeout(type, speed)
} else {
setDone(true)
onComplete?.()
}
}
timeoutRef.current = setTimeout(type, delay)
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current)
}
}, [started, text, speed, delay, reducedMotion, onComplete])
return { displayed, done, start }
}

148
src/index.css Normal file
View File

@@ -0,0 +1,148 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-skin-50: #FDF8F3;
--color-skin-100: #FAF0E6;
--color-skin-200: #F5E1D0;
--color-skin-300: #EBCDB0;
--color-pink-brand: #FF2D7A;
--color-pink-bright: #FF1B8D;
--color-pink-soft: #FF6B9D;
--color-pink-pale: #FFB6C1;
--color-grey-900: #1A1A1A;
--color-grey-800: #2A2A2A;
--color-grey-700: #3A3A3A;
--color-grey-600: #555555;
--color-grey-500: #777777;
--font-mono: 'Roboto Mono', monospace;
--font-display: 'Space Grotesk', sans-serif;
--font-body: 'DM Sans', sans-serif;
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out-circ: cubic-bezier(0.85, 0, 0.15, 1);
}
html {
scroll-behavior: auto;
}
body {
font-family: var(--font-body);
background-color: var(--color-skin-50);
color: var(--color-grey-900);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
::selection {
background-color: var(--color-pink-brand);
color: white;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--color-skin-100);
}
::-webkit-scrollbar-thumb {
background: var(--color-pink-soft);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-pink-brand);
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Utility classes */
.text-gradient {
background: linear-gradient(135deg, var(--color-pink-brand) 0%, var(--color-pink-bright) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.glass {
background: rgba(253, 248, 243, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 45, 122, 0.1);
}
/* Ambient floating shapes */
@keyframes ambient-float-1 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
33% { transform: translate(30px, -50px) rotate(120deg); }
66% { transform: translate(-20px, -30px) rotate(240deg); }
}
@keyframes ambient-float-2 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
33% { transform: translate(-40px, 30px) rotate(-120deg); }
66% { transform: translate(20px, 50px) rotate(-240deg); }
}
@keyframes ambient-float-3 {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(15px, -25px) scale(1.05); }
}
.ambient-shape-1 {
animation: ambient-float-1 12s ease-in-out infinite;
}
.ambient-shape-2 {
animation: ambient-float-2 15s ease-in-out infinite;
}
.ambient-shape-3 {
animation: ambient-float-3 8s ease-in-out infinite;
}
/* Typing cursor */
.typing-cursor::after {
content: '|';
animation: blink 1s step-end infinite;
color: var(--color-pink-brand);
margin-left: 2px;
}
@keyframes blink {
50% { opacity: 0; }
}
/* Line reveal */
.line-reveal {
overflow: hidden;
}
.line-reveal > span {
display: inline-block;
transform: translateY(100%);
opacity: 0;
transition: transform 0.8s var(--ease-out-expo), opacity 0.8s var(--ease-out-expo);
}
.line-reveal.revealed > span {
transform: translateY(0);
opacity: 1;
}

10
src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

53
tailwind.config.js Normal file
View File

@@ -0,0 +1,53 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
skin: {
50: '#FDF8F3',
100: '#FAF0E6',
200: '#F5E1D0',
300: '#EBCDB0',
},
pink: {
brand: '#FF2D7A',
bright: '#FF1B8D',
soft: '#FF6B9D',
pale: '#FFB6C1',
},
grey: {
900: '#1A1A1A',
800: '#2A2A2A',
700: '#3A3A3A',
600: '#555555',
500: '#777777',
},
},
fontFamily: {
mono: ['"Roboto Mono"', 'monospace'],
display: ['"Space Grotesk"', 'sans-serif'],
body: ['"DM Sans"', 'sans-serif'],
},
animation: {
'float': 'float 6s ease-in-out infinite',
'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'drift': 'drift 20s linear infinite',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-20px)' },
},
drift: {
'0%': { transform: 'translateX(-100%) translateY(0)' },
'100%': { transform: 'translateX(100vw) translateY(-40px)' },
},
},
},
},
plugins: [],
}

21
tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
tsconfig.node.json Normal file
View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

7
vite.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: './',
})