back to fresh
This commit is contained in:
-16
@@ -1,16 +0,0 @@
|
|||||||
# Stage 1: Build the application
|
|
||||||
FROM node:20-alpine AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install
|
|
||||||
COPY . .
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# Stage 2: Serve the static files with Nginx
|
|
||||||
FROM nginx:alpine
|
|
||||||
# Copy the build files from the builder stage to the Nginx web directory
|
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
||||||
# Expose port 80 where the Nginx server runs
|
|
||||||
EXPOSE 80
|
|
||||||
# Command to start Nginx server
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# 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 [Oxc](https://oxc.rs)
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
||||||
|
|
||||||
## 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,23 +0,0 @@
|
|||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Selimaj.dev | Klesti Selimaj</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Generated
-10111
File diff suppressed because it is too large
Load Diff
@@ -1,84 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "selimaj-portfolio",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite --host",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview --host",
|
|
||||||
"check": "tsc --noEmit",
|
|
||||||
"format": "prettier --write ."
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@hookform/resolvers": "^5.2.2",
|
|
||||||
"@radix-ui/react-accordion": "^1.2.12",
|
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
|
||||||
"@radix-ui/react-avatar": "^1.1.10",
|
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
|
||||||
"@radix-ui/react-collapsible": "^1.1.12",
|
|
||||||
"@radix-ui/react-context-menu": "^2.2.16",
|
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
||||||
"@radix-ui/react-hover-card": "^1.1.15",
|
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
|
||||||
"@radix-ui/react-menubar": "^1.1.16",
|
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.14",
|
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
|
||||||
"@radix-ui/react-progress": "^1.1.7",
|
|
||||||
"@radix-ui/react-radio-group": "^1.3.8",
|
|
||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
|
||||||
"@radix-ui/react-slider": "^1.3.6",
|
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
|
||||||
"@radix-ui/react-switch": "^1.2.6",
|
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
|
||||||
"@radix-ui/react-toggle": "^1.1.10",
|
|
||||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
|
||||||
"axios": "^1.12.0",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"cmdk": "^1.1.1",
|
|
||||||
"embla-carousel-react": "^8.6.0",
|
|
||||||
"framer-motion": "^12.23.22",
|
|
||||||
"input-otp": "^1.4.2",
|
|
||||||
"lucide-react": "^0.453.0",
|
|
||||||
"nanoid": "^5.1.5",
|
|
||||||
"next-themes": "^0.4.6",
|
|
||||||
"react": "^19.2.1",
|
|
||||||
"react-day-picker": "^9.11.1",
|
|
||||||
"react-dom": "^19.2.1",
|
|
||||||
"react-hook-form": "^7.64.0",
|
|
||||||
"react-resizable-panels": "^3.0.6",
|
|
||||||
"recharts": "^2.15.2",
|
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"streamdown": "^1.4.0",
|
|
||||||
"tailwind-merge": "^3.3.1",
|
|
||||||
"tailwindcss-animate": "^1.0.7",
|
|
||||||
"vaul": "^1.1.2",
|
|
||||||
"wouter": "^3.3.5",
|
|
||||||
"zod": "^4.1.12"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
|
||||||
"@types/node": "^24.7.0",
|
|
||||||
"@types/react": "^19.2.1",
|
|
||||||
"@types/react-dom": "^19.2.1",
|
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
|
||||||
"autoprefixer": "^10.4.20",
|
|
||||||
"pnpm": "^10.15.1",
|
|
||||||
"postcss": "^8.4.47",
|
|
||||||
"prettier": "^3.6.2",
|
|
||||||
"tailwindcss": "^4.2.1",
|
|
||||||
"tsx": "^4.19.1",
|
|
||||||
"tw-animate-css": "^1.4.0",
|
|
||||||
"typescript": "5.6.3",
|
|
||||||
"vite": "^7.1.7",
|
|
||||||
"vitest": "^2.1.4"
|
|
||||||
},
|
|
||||||
"packageManager": "[email protected]"
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.8 KiB |
-32
@@ -1,32 +0,0 @@
|
|||||||
import { Toaster } from "@/components/ui/sonner";
|
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
||||||
import NotFound from "@/pages/NotFound";
|
|
||||||
import { Route, Switch } from "wouter";
|
|
||||||
import ErrorBoundary from "./components/ErrorBoundary";
|
|
||||||
import { ThemeProvider } from "./contexts/ThemeContext";
|
|
||||||
import Home from "./pages/Home";
|
|
||||||
|
|
||||||
function Router() {
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Route path={"/"} component={Home} />
|
|
||||||
<Route path={"/404"} component={NotFound} />
|
|
||||||
<Route component={NotFound} />
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
|
||||||
<ErrorBoundary>
|
|
||||||
<ThemeProvider defaultTheme="dark">
|
|
||||||
<TooltipProvider>
|
|
||||||
<Toaster />
|
|
||||||
<Router />
|
|
||||||
</TooltipProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
/*
|
|
||||||
* ABOUT SECTION — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Asymmetric layout: text left, abstract visual right
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { EarthIcon, LayersIcon, TargetIcon, ZapIcon } from "lucide-react";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
const ABOUT_VISUAL =
|
|
||||||
"/logo.png";
|
|
||||||
|
|
||||||
function useReveal() {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
const el = ref.current;
|
|
||||||
if (!el) return;
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
el.classList.add("visible");
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold: 0.15 },
|
|
||||||
);
|
|
||||||
observer.observe(el);
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, []);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
const highlights = [
|
|
||||||
{ Icon: ZapIcon, label: "Currently", value: "Grinding on SaaS products" },
|
|
||||||
{ Icon: LayersIcon, label: "Fav Stack", value: "Rust · TypeScript · Linux" },
|
|
||||||
{ Icon: TargetIcon, label: "Focus", value: "Low-level systems & UI/UX" },
|
|
||||||
{ Icon: EarthIcon, label: "Based in", value: "Albania" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function AboutSection() {
|
|
||||||
const ref1 = useReveal();
|
|
||||||
const ref2 = useReveal();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
id="about"
|
|
||||||
className="py-32 relative"
|
|
||||||
style={{ background: "#080808" }}
|
|
||||||
>
|
|
||||||
{/* Subtle top border */}
|
|
||||||
<div className="absolute top-0 left-0 right-0 h-px bg-linear-to-r from-transparent via-white/10 to-transparent" />
|
|
||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-6">
|
|
||||||
{/* Section label */}
|
|
||||||
<div className="terminal-label mb-4">> about.me</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
|
||||||
{/* Left: Text */}
|
|
||||||
<div ref={ref1} className="reveal">
|
|
||||||
<h2 className="section-heading text-4xl sm:text-5xl text-white mb-8 leading-tight">
|
|
||||||
Building the future, <span className="text-white/40">one commit at a time.</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="space-y-4 text-white/60 leading-relaxed"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
I'm{" "}
|
|
||||||
<strong className="text-white font-semibold">
|
|
||||||
Klesti Selimaj
|
|
||||||
</strong>{" "}
|
|
||||||
— a 16-year-old software developer and entrepreneur from Albania
|
|
||||||
with 6 years of hands-on experience. I started coding at 10 and
|
|
||||||
never stopped.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
My passion lies in{" "}
|
|
||||||
<strong className="text-white/80">
|
|
||||||
low-level systems programming
|
|
||||||
</strong>
|
|
||||||
, building developer tools, and crafting polished user
|
|
||||||
experiences. I work primarily in{" "}
|
|
||||||
<strong className="text-white/80">Rust</strong> and{" "}
|
|
||||||
<strong className="text-white/80">TypeScript</strong>, and I'm
|
|
||||||
deeply interested in hacking, Linux internals, and TUI
|
|
||||||
applications.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Beyond code, I'm building towards a successful SaaS product —
|
|
||||||
combining my technical depth with an entrepreneurial mindset to
|
|
||||||
create tools that developers actually want to use.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Highlights grid */}
|
|
||||||
<div className="mt-10 grid grid-cols-2 gap-3">
|
|
||||||
{highlights.map((h) => (
|
|
||||||
<div key={h.label} className="glass rounded-xl p-4 card-hover">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="text-lg">{<h.Icon />}</div>
|
|
||||||
<div>
|
|
||||||
<div className="terminal-label text-xs mb-0.5">
|
|
||||||
{h.label}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="text-sm text-white/80 font-medium"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
{h.value}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Social links */}
|
|
||||||
<div className="mt-8 flex flex-wrap gap-3">
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
label: "GitHub",
|
|
||||||
href: "https://github.com/selimaj-dev",
|
|
||||||
icon: "⌥",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "YouTube",
|
|
||||||
href: "https://youtube.com/@SelimajDev",
|
|
||||||
icon: "▶",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Instagram",
|
|
||||||
href: "https://instagram.com/selimaj.dev",
|
|
||||||
icon: "◈",
|
|
||||||
},
|
|
||||||
].map((s) => (
|
|
||||||
<a
|
|
||||||
key={s.label}
|
|
||||||
href={s.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="skill-chip hover:no-underline"
|
|
||||||
>
|
|
||||||
<span className="text-white/40">{s.icon}</span>
|
|
||||||
{s.label}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right: Visual */}
|
|
||||||
<div ref={ref2} className="reveal flex justify-center lg:justify-end">
|
|
||||||
<div className="relative w-full max-w-md">
|
|
||||||
{/* Glow behind image */}
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 rounded-2xl blur-3xl opacity-20"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
"radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 70%)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="relative glass rounded-2xl overflow-hidden float-anim">
|
|
||||||
<img
|
|
||||||
src={ABOUT_VISUAL}
|
|
||||||
alt="Abstract tech visualization"
|
|
||||||
className="w-full h-auto object-cover"
|
|
||||||
style={{ aspectRatio: "1/1" }}
|
|
||||||
/>
|
|
||||||
{/* Overlay with terminal text */}
|
|
||||||
<div
|
|
||||||
className="absolute bottom-0 left-0 right-0 p-5"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
"linear-gradient(to top, rgba(8,8,8,0.9) 0%, transparent 100%)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="terminal-label text-xs">
|
|
||||||
<span className="text-white/30">$</span>{" "}
|
|
||||||
<span className="text-white/60">whoami</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="text-white/80 text-sm mt-1"
|
|
||||||
style={{ fontFamily: "'JetBrains Mono', monospace" }}
|
|
||||||
>
|
|
||||||
Klesti Selimaj
|
|
||||||
</div>
|
|
||||||
<div className="terminal-label text-xs mt-0.5">
|
|
||||||
uid=1000(klesti) groups=ORUS,Axiom,Saturn,Nexa
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
/*
|
|
||||||
* CONTACT SECTION — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Clean contact links with glass cards
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { Github, Youtube, Instagram, ExternalLink, Mail, MailIcon } from "lucide-react";
|
|
||||||
|
|
||||||
function useReveal(threshold = 0.1) {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
const el = ref.current;
|
|
||||||
if (!el) return;
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
el.classList.add("visible");
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold }
|
|
||||||
);
|
|
||||||
observer.observe(el);
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, [threshold]);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contactLinks = [
|
|
||||||
{
|
|
||||||
icon: Github,
|
|
||||||
label: "GitHub",
|
|
||||||
handle: "@selimaj-dev",
|
|
||||||
href: "https://github.com/selimaj-dev",
|
|
||||||
description: "97+ repositories · 2,670 contributions/year",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Youtube,
|
|
||||||
label: "YouTube",
|
|
||||||
handle: "@SelimajDev",
|
|
||||||
href: "https://youtube.com/@SelimajDev",
|
|
||||||
description: "Dev content, tutorials, and builds",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Instagram,
|
|
||||||
label: "Instagram",
|
|
||||||
handle: "@selimaj.dev",
|
|
||||||
href: "https://instagram.com/selimaj.dev",
|
|
||||||
description: "Behind the scenes and updates",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: MailIcon,
|
|
||||||
label: "Email",
|
|
||||||
handle: "[email protected]",
|
|
||||||
href: "mailto:[email protected]",
|
|
||||||
description: "For business inquiries, collaborations, and contact",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function ContactSection() {
|
|
||||||
const headerRef = useReveal();
|
|
||||||
const contentRef = useReveal(0.1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="contact" className="py-32 relative" style={{ background: "#080808" }}>
|
|
||||||
<div className="absolute top-0 left-0 right-0 h-px bg-linear-to-r from-transparent via-white/10 to-transparent" />
|
|
||||||
|
|
||||||
{/* Subtle radial glow */}
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 pointer-events-none"
|
|
||||||
style={{
|
|
||||||
background: "radial-gradient(ellipse 60% 40% at 50% 100%, rgba(255,255,255,0.03) 0%, transparent 70%)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div ref={headerRef} className="reveal mb-16 text-center">
|
|
||||||
<div className="terminal-label mb-4 text-center">> contact --open</div>
|
|
||||||
<h2 className="section-heading text-4xl sm:text-5xl text-white leading-tight mb-6">
|
|
||||||
Let's Build
|
|
||||||
<br />
|
|
||||||
<span className="text-white/40">Something Together</span>
|
|
||||||
</h2>
|
|
||||||
<p
|
|
||||||
className="text-white/50 max-w-xl mx-auto text-base leading-relaxed"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
I'm always open to interesting projects, collaborations, or just a good conversation
|
|
||||||
about tech. Reach out through any of the channels below.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contact cards */}
|
|
||||||
<div ref={contentRef} className="reveal">
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-12">
|
|
||||||
{contactLinks.map((link) => {
|
|
||||||
const Icon = link.icon;
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
key={link.label}
|
|
||||||
href={link.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="glass rounded-2xl p-6 card-hover group no-underline flex flex-col gap-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-10 h-10 glass-strong rounded-xl flex items-center justify-center">
|
|
||||||
<Icon size={18} className="text-white/70 group-hover:text-white transition-colors" />
|
|
||||||
</div>
|
|
||||||
<ExternalLink size={12} className="text-white/20 group-hover:text-white/50 transition-colors" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
className="text-white font-semibold text-sm mb-0.5"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
{link.label}
|
|
||||||
</div>
|
|
||||||
<div className="terminal-label text-xs mb-2">{link.handle}</div>
|
|
||||||
<div
|
|
||||||
className="text-white/40 text-xs leading-relaxed"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
{link.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Big CTA */}
|
|
||||||
<div className="glass rounded-2xl p-10 text-center">
|
|
||||||
<div className="terminal-label mb-4">> open_to_work --status</div>
|
|
||||||
<h3
|
|
||||||
className="text-2xl sm:text-3xl font-bold text-white mb-4"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
Open to freelance & collaboration
|
|
||||||
</h3>
|
|
||||||
<p
|
|
||||||
className="text-white/50 mb-8 max-w-lg mx-auto text-sm leading-relaxed"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
Looking for interesting projects, SaaS co-founders, or clients who need
|
|
||||||
a developer who ships fast and cares about quality.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
|
||||||
<a
|
|
||||||
href="https://github.com/selimaj-dev"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="px-8 py-3.5 bg-white text-black font-semibold rounded-xl hover:bg-white/90 transition-all duration-200 text-sm no-underline"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
View GitHub Profile
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://instagram.com/selimaj.dev"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="px-8 py-3.5 glass border border-white/10 text-white/70 font-medium rounded-xl hover:text-white hover:border-white/20 transition-all duration-200 text-sm no-underline"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
DM on Instagram
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { AlertTriangle, RotateCcw } from "lucide-react";
|
|
||||||
import { Component, type ReactNode } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
hasError: boolean;
|
|
||||||
error: Error | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorBoundary extends Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { hasError: false, error: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getDerivedStateFromError(error: Error): State {
|
|
||||||
return { hasError: true, error };
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center min-h-screen p-8 bg-background">
|
|
||||||
<div className="flex flex-col items-center w-full max-w-2xl p-8">
|
|
||||||
<AlertTriangle
|
|
||||||
size={48}
|
|
||||||
className="text-destructive mb-6 shrink-0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h2 className="text-xl mb-4">An unexpected error occurred.</h2>
|
|
||||||
|
|
||||||
<div className="p-4 w-full rounded bg-muted overflow-auto mb-6">
|
|
||||||
<pre className="text-sm text-muted-foreground whitespace-break-spaces">
|
|
||||||
{this.state.error?.stack}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => window.location.reload()}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2 px-4 py-2 rounded-lg",
|
|
||||||
"bg-primary text-primary-foreground",
|
|
||||||
"hover:opacity-90 cursor-pointer"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<RotateCcw size={16} />
|
|
||||||
Reload Page
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ErrorBoundary;
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* FOOTER — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Minimal footer with branding and links
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function Footer() {
|
|
||||||
const year = new Date().getFullYear();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<footer
|
|
||||||
className="py-10 relative"
|
|
||||||
style={{
|
|
||||||
background: "#080808",
|
|
||||||
borderTop: "1px solid rgba(255,255,255,0.06)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="max-w-6xl mx-auto px-6">
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
|
||||||
{/* Brand */}
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span
|
|
||||||
className="text-white font-bold text-base tracking-tight"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
selimaj<span className="text-white/30">.dev</span>
|
|
||||||
</span>
|
|
||||||
<span className="text-white/20 text-xs">·</span>
|
|
||||||
<span className="terminal-label text-xs">Klesti Selimaj</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Links */}
|
|
||||||
<div className="flex items-center gap-6">
|
|
||||||
{[
|
|
||||||
{ label: "GitHub", href: "https://github.com/selimaj-dev" },
|
|
||||||
{ label: "YouTube", href: "https://youtube.com/@SelimajDev" },
|
|
||||||
{ label: "Instagram", href: "https://instagram.com/selimaj.dev" },
|
|
||||||
].map((link) => (
|
|
||||||
<a
|
|
||||||
key={link.label}
|
|
||||||
href={link.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="terminal-label text-xs hover:text-white/70 transition-colors"
|
|
||||||
>
|
|
||||||
{link.label}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Copyright */}
|
|
||||||
<div className="terminal-label text-xs">
|
|
||||||
© {year} selimaj.dev
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
/*
|
|
||||||
* HERO SECTION — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Full-viewport dark hero with animated background image, typewriter effect, and glass CTA
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
const HERO_BG = "https://d2xsxph8kpxj0f.cloudfront.net/310519663439135967/RkRL4wd7YzNoLyaN7suWbR/hero-bg-G3393XyTzqxu3D4dR63od8.webp";
|
|
||||||
|
|
||||||
const typewriterPhrases = [
|
|
||||||
"Software Developer.",
|
|
||||||
"Entrepreneur.",
|
|
||||||
"SaaS Builder.",
|
|
||||||
"Systems Programmer.",
|
|
||||||
"Open Source Contributor.",
|
|
||||||
];
|
|
||||||
|
|
||||||
function useTypewriter(phrases: string[], speed = 60, pause = 1800) {
|
|
||||||
const [display, setDisplay] = useState("");
|
|
||||||
const [phraseIdx, setPhraseIdx] = useState(0);
|
|
||||||
const [charIdx, setCharIdx] = useState(0);
|
|
||||||
const [deleting, setDeleting] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const current = phrases[phraseIdx];
|
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
|
||||||
|
|
||||||
if (!deleting && charIdx < current.length) {
|
|
||||||
timeout = setTimeout(() => setCharIdx((c) => c + 1), speed);
|
|
||||||
} else if (!deleting && charIdx === current.length) {
|
|
||||||
timeout = setTimeout(() => setDeleting(true), pause);
|
|
||||||
} else if (deleting && charIdx > 0) {
|
|
||||||
timeout = setTimeout(() => setCharIdx((c) => c - 1), speed / 2);
|
|
||||||
} else if (deleting && charIdx === 0) {
|
|
||||||
setDeleting(false);
|
|
||||||
setPhraseIdx((i) => (i + 1) % phrases.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
setDisplay(current.slice(0, charIdx));
|
|
||||||
return () => clearTimeout(timeout);
|
|
||||||
}, [charIdx, deleting, phraseIdx, phrases, speed, pause]);
|
|
||||||
|
|
||||||
return display;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HeroSection() {
|
|
||||||
const typed = useTypewriter(typewriterPhrases);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
id="hero"
|
|
||||||
className="relative min-h-screen flex items-center justify-center overflow-hidden"
|
|
||||||
style={{ background: "#080808" }}
|
|
||||||
>
|
|
||||||
{/* Background image */}
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url(${HERO_BG})`,
|
|
||||||
opacity: 0.55,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Gradient overlays */}
|
|
||||||
<div className="absolute inset-0 bg-linear-to-b from-transparent via-transparent to-[#080808]" />
|
|
||||||
<div className="absolute inset-0 bg-linear-to-r from-[#080808]/60 via-transparent to-[#080808]/60" />
|
|
||||||
|
|
||||||
{/* Animated glow orb */}
|
|
||||||
<div
|
|
||||||
className="absolute top-1/3 left-1/2 -translate-x-1/2 -translate-y-1/2 w-150 h-150 rounded-full pointer-events-none"
|
|
||||||
style={{
|
|
||||||
background: "radial-gradient(circle, rgba(255,255,255,0.04) 0%, transparent 70%)",
|
|
||||||
animation: "float 6s ease-in-out infinite",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="relative z-10 max-w-5xl mx-auto px-6 text-center">
|
|
||||||
{/* Terminal label */}
|
|
||||||
<div className="inline-flex items-center gap-2 mb-8 px-4 py-2 glass rounded-full">
|
|
||||||
<span className="w-2 h-2 rounded-full bg-white/60 inline-block" />
|
|
||||||
<span className="terminal-label text-xs">
|
|
||||||
> klesti@selimaj.dev ~ $
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main heading */}
|
|
||||||
<h1
|
|
||||||
className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl font-bold text-white mb-6 leading-none tracking-tight fade-in-up"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
Klesti
|
|
||||||
<br />
|
|
||||||
<span className="text-white/30">Selimaj</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{/* Typewriter subtitle */}
|
|
||||||
<div
|
|
||||||
className="text-xl sm:text-2xl md:text-3xl font-medium text-white/70 mb-4 h-10 fade-in-up delay-200"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
{typed}
|
|
||||||
<span className="cursor-blink text-white/50 ml-0.5">|</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
<p
|
|
||||||
className="text-base sm:text-lg text-white/45 max-w-2xl mx-auto mb-12 leading-relaxed fade-in-up delay-400"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
16 years old · 6 years of experience · Albania
|
|
||||||
<br />
|
|
||||||
Passionate about low-level systems, hacking, UI/UX, and making cool tools.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* CTA Buttons */}
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 fade-in-up delay-500">
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
document.querySelector("#projects")?.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}}
|
|
||||||
className="px-8 py-3.5 bg-white text-black font-semibold rounded-xl hover:bg-white/90 transition-all duration-200 text-sm"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
View Projects
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
document.querySelector("#contact")?.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}}
|
|
||||||
className="px-8 py-3.5 glass text-white/80 font-medium rounded-xl hover:text-white hover:border-white/20 transition-all duration-200 text-sm border border-white/10"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
Get in Touch
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats row */}
|
|
||||||
<div className="mt-20 flex flex-row flex-wrap items-center justify-center gap-6 sm:gap-12 fade-in-up delay-700">
|
|
||||||
{[
|
|
||||||
{ value: "6+", label: "Years Experience" },
|
|
||||||
{ value: "97+", label: "Repositories" },
|
|
||||||
{ value: "2,670+", label: "Contributions / Year" },
|
|
||||||
{ value: "#2", label: "National GitHub user" },
|
|
||||||
].map((stat) => (
|
|
||||||
<div key={stat.label} className="text-center">
|
|
||||||
<div
|
|
||||||
className="text-3xl font-bold text-white"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
{stat.value}
|
|
||||||
</div>
|
|
||||||
<div className="terminal-label mt-1">{stat.label}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Scroll indicator */}
|
|
||||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 opacity-0 fade-in-up delay-800" style={{animationFillMode:'forwards'}}>
|
|
||||||
<div className="w-px h-8 bg-linear-to-b from-white/30 to-transparent" />
|
|
||||||
<span className="terminal-label text-xs">scroll</span>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
/*
|
|
||||||
* NAVBAR — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Glass effect: backdrop-blur + rgba background
|
|
||||||
* Becomes more opaque on scroll
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
const navLinks = [
|
|
||||||
{ label: "About", href: "#about" },
|
|
||||||
{ label: "Projects", href: "#projects" },
|
|
||||||
{ label: "Skills", href: "#skills" },
|
|
||||||
{ label: "Contact", href: "#contact" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Navbar() {
|
|
||||||
const [scrolled, setScrolled] = useState(false);
|
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleScroll = () => {
|
|
||||||
setScrolled(window.scrollY > 40);
|
|
||||||
};
|
|
||||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
||||||
return () => window.removeEventListener("scroll", handleScroll);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleNavClick = (href: string) => {
|
|
||||||
setMenuOpen(false);
|
|
||||||
const el = document.querySelector(href);
|
|
||||||
if (el) {
|
|
||||||
el.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav
|
|
||||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
|
|
||||||
scrolled ? "navbar-glass-solid" : "navbar-glass"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="max-w-6xl mx-auto px-6 flex items-center justify-between h-16">
|
|
||||||
{/* Logo */}
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => { e.preventDefault(); window.scrollTo({ top: 0, behavior: "smooth" }); }}
|
|
||||||
className="flex items-center gap-2 group"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="text-white font-bold text-lg tracking-tight"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
selimaj<span className="text-white/40">.dev</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{/* Desktop nav links */}
|
|
||||||
<div className="hidden md:flex items-center gap-1">
|
|
||||||
{navLinks.map((link) => (
|
|
||||||
<button
|
|
||||||
key={link.href}
|
|
||||||
onClick={() => handleNavClick(link.href)}
|
|
||||||
className="px-4 py-2 text-sm text-white/60 hover:text-white transition-colors duration-200 rounded-lg hover:bg-white/5"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
{link.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
<a
|
|
||||||
href="https://github.com/selimaj-dev"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="ml-3 px-4 py-2 text-sm font-medium text-black bg-white rounded-lg hover:bg-white/90 transition-all duration-200"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile hamburger */}
|
|
||||||
<button
|
|
||||||
className="md:hidden flex flex-col gap-1.5 p-2"
|
|
||||||
onClick={() => setMenuOpen(!menuOpen)}
|
|
||||||
aria-label="Toggle menu"
|
|
||||||
>
|
|
||||||
<span className={`block w-5 h-0.5 bg-white/70 transition-all duration-300 ${menuOpen ? "rotate-45 translate-y-2" : ""}`} />
|
|
||||||
<span className={`block w-5 h-0.5 bg-white/70 transition-all duration-300 ${menuOpen ? "opacity-0" : ""}`} />
|
|
||||||
<span className={`block w-5 h-0.5 bg-white/70 transition-all duration-300 ${menuOpen ? "-rotate-45 -translate-y-2" : ""}`} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile menu */}
|
|
||||||
<div
|
|
||||||
className={`md:hidden overflow-hidden transition-all duration-300 ${
|
|
||||||
menuOpen ? "max-h-64 opacity-100" : "max-h-0 opacity-0"
|
|
||||||
}`}
|
|
||||||
style={{ background: "rgba(8,8,8,0.95)", borderTop: "1px solid rgba(255,255,255,0.06)" }}
|
|
||||||
>
|
|
||||||
<div className="px-6 py-4 flex flex-col gap-1">
|
|
||||||
{navLinks.map((link) => (
|
|
||||||
<button
|
|
||||||
key={link.href}
|
|
||||||
onClick={() => handleNavClick(link.href)}
|
|
||||||
className="text-left px-3 py-3 text-sm text-white/60 hover:text-white transition-colors rounded-lg hover:bg-white/5"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
{link.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
<a
|
|
||||||
href="https://github.com/selimaj-dev"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="mt-2 px-3 py-3 text-sm font-medium text-black bg-white rounded-lg text-center hover:bg-white/90 transition-all"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
/*
|
|
||||||
* PROJECTS SECTION — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Masonry-style grid with glass cards, language badges, and star counts
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { ExternalLink, Star, GitFork } from "lucide-react";
|
|
||||||
|
|
||||||
function useReveal(threshold = 0.1) {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
const el = ref.current;
|
|
||||||
if (!el) return;
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
el.classList.add("visible");
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold },
|
|
||||||
);
|
|
||||||
observer.observe(el);
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, [threshold]);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LANG_COLORS: Record<string, string> = {
|
|
||||||
TypeScript: "#3178c6",
|
|
||||||
Rust: "#ce422b",
|
|
||||||
Java: "#b07219",
|
|
||||||
JavaScript: "#f1e05a",
|
|
||||||
Python: "#3572A5",
|
|
||||||
Shell: "#89e051",
|
|
||||||
"C#": "#178600",
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Project {
|
|
||||||
name: string;
|
|
||||||
org?: string;
|
|
||||||
description: string;
|
|
||||||
language: string;
|
|
||||||
stars: number;
|
|
||||||
forks?: number;
|
|
||||||
href: string;
|
|
||||||
featured?: boolean;
|
|
||||||
tag?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects: Project[] = [
|
|
||||||
{
|
|
||||||
name: "osui",
|
|
||||||
org: "osui-rs",
|
|
||||||
description:
|
|
||||||
"A TUI library written in Rust — build terminal UIs with a component-based approach.",
|
|
||||||
language: "Rust",
|
|
||||||
stars: 7,
|
|
||||||
href: "https://github.com/osui-rs/osui",
|
|
||||||
featured: true,
|
|
||||||
tag: "Library",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "saturnclient",
|
|
||||||
org: "saturnclientmc",
|
|
||||||
description:
|
|
||||||
"Saturn Client — a Minecraft client full of customizability and free cosmetics.",
|
|
||||||
language: "Java",
|
|
||||||
stars: 7,
|
|
||||||
forks: 2,
|
|
||||||
href: "https://github.com/saturnclientmc/saturnclient",
|
|
||||||
featured: true,
|
|
||||||
tag: "Game Client",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dev-run",
|
|
||||||
org: "orus-dev",
|
|
||||||
description:
|
|
||||||
"DevRun — a community-driven competitive programming platform inspired by speedrunning.",
|
|
||||||
language: "TypeScript",
|
|
||||||
stars: 3,
|
|
||||||
href: "https://github.com/orus-dev/dev-run",
|
|
||||||
featured: true,
|
|
||||||
tag: "Platform",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nexa-trade",
|
|
||||||
org: "nexa-sol",
|
|
||||||
description:
|
|
||||||
"The Solana trading terminal supercharged by AI — trade smarter.",
|
|
||||||
language: "TypeScript",
|
|
||||||
stars: 1,
|
|
||||||
href: "https://github.com/nexa-sol/nexa-trade",
|
|
||||||
featured: true,
|
|
||||||
tag: "FinTech",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dockship",
|
|
||||||
org: "orus-dev",
|
|
||||||
description:
|
|
||||||
"A powerful self-hosted deployment platform for managing your applications.",
|
|
||||||
language: "TypeScript",
|
|
||||||
stars: 1,
|
|
||||||
href: "https://github.com/orus-dev/dockship",
|
|
||||||
tag: "DevOps",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "axiom",
|
|
||||||
org: "AxiomChat",
|
|
||||||
description: "A modern real-time chat application built with TypeScript.",
|
|
||||||
language: "TypeScript",
|
|
||||||
stars: 1,
|
|
||||||
href: "https://github.com/AxiomChat/axiom",
|
|
||||||
tag: "App",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "linear-linux",
|
|
||||||
description:
|
|
||||||
"Linux support for linear.app — bringing the popular project management tool to Linux.",
|
|
||||||
language: "JavaScript",
|
|
||||||
stars: 27,
|
|
||||||
forks: 5,
|
|
||||||
href: "https://github.com/selimaj-dev/linear-linux",
|
|
||||||
tag: "Tool",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "osmium",
|
|
||||||
description: "A TUI component-based game engine built on top of OSUI.",
|
|
||||||
language: "Rust",
|
|
||||||
stars: 1,
|
|
||||||
href: "https://github.com/selimaj-dev/osmium",
|
|
||||||
tag: "Engine",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "spark",
|
|
||||||
description:
|
|
||||||
"AI documentation generator that takes context from your repository's source code.",
|
|
||||||
language: "Python",
|
|
||||||
stars: 2,
|
|
||||||
href: "https://github.com/selimaj-dev/spark",
|
|
||||||
tag: "AI Tool",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "flux",
|
|
||||||
description:
|
|
||||||
"A tool and library for using multiple languages in one codebase seamlessly.",
|
|
||||||
language: "Rust",
|
|
||||||
stars: 1,
|
|
||||||
href: "https://github.com/selimaj-dev/flux",
|
|
||||||
tag: "Tool",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "session-rs",
|
|
||||||
description: "A lightweight async WebSocket protocol library for Rust.",
|
|
||||||
language: "Rust",
|
|
||||||
stars: 1,
|
|
||||||
href: "https://github.com/selimaj-dev/session-rs",
|
|
||||||
tag: "Library",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tailwind-motlib",
|
|
||||||
description: "A motion and stagger animation library for Tailwind CSS.",
|
|
||||||
language: "TypeScript",
|
|
||||||
stars: 0,
|
|
||||||
href: "https://github.com/selimaj-dev/tailwind-motlib",
|
|
||||||
tag: "Library",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function ProjectCard({ project, delay }: { project: Project; delay: number }) {
|
|
||||||
const langColor = LANG_COLORS[project.language] || "#888";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={project.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block glass rounded-2xl p-6 card-hover group no-underline"
|
|
||||||
style={{ animationDelay: `${delay}ms` }}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-start justify-between mb-3">
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
{project.tag && (
|
|
||||||
<span className="terminal-label text-xs inline-block mb-2 px-2 py-0.5 rounded-full border border-white/10 bg-white/5">
|
|
||||||
{project.tag}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<h3
|
|
||||||
className="text-white font-semibold text-base leading-tight truncate"
|
|
||||||
style={{ fontFamily: "'Syne', sans-serif" }}
|
|
||||||
>
|
|
||||||
{project.org ? (
|
|
||||||
<span className="text-white/40">{project.org}/</span>
|
|
||||||
) : null}
|
|
||||||
{project.name}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<ExternalLink
|
|
||||||
size={14}
|
|
||||||
className="text-white/20 group-hover:text-white/60 transition-colors ml-3 mt-1 shrink-0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
<p
|
|
||||||
className="text-white/50 text-sm leading-relaxed mb-5 line-clamp-2"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
{project.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="lang-badge">
|
|
||||||
<span className="lang-dot" style={{ backgroundColor: langColor }} />
|
|
||||||
{project.language}
|
|
||||||
</span>
|
|
||||||
<span className="lang-badge">
|
|
||||||
<Star size={11} className="text-white/40" />
|
|
||||||
{project.stars}
|
|
||||||
</span>
|
|
||||||
{project.forks !== undefined && (
|
|
||||||
<span className="lang-badge">
|
|
||||||
<GitFork size={11} className="text-white/40" />
|
|
||||||
{project.forks}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ProjectsSection() {
|
|
||||||
const headerRef = useReveal();
|
|
||||||
const gridRef = useReveal(0.05);
|
|
||||||
|
|
||||||
const featured = projects.filter((p) => p.featured);
|
|
||||||
const rest = projects.filter((p) => !p.featured);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
id="projects"
|
|
||||||
className="py-32 relative"
|
|
||||||
style={{ background: "#080808" }}
|
|
||||||
>
|
|
||||||
<div className="absolute top-0 left-0 right-0 h-px bg-linear-to-r from-transparent via-white/10 to-transparent" />
|
|
||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div ref={headerRef} className="reveal mb-16">
|
|
||||||
<div className="terminal-label mb-4">> ls projects/</div>
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-4">
|
|
||||||
<h2 className="section-heading text-4xl sm:text-5xl text-white leading-tight">
|
|
||||||
Things I've <span className="text-white/40">Built</span>
|
|
||||||
</h2>
|
|
||||||
<a
|
|
||||||
href="https://github.com/selimaj-dev?tab=repositories"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-sm text-white/40 hover:text-white transition-colors terminal-label"
|
|
||||||
>
|
|
||||||
View all 35+ repos →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Featured projects (larger cards) */}
|
|
||||||
<div ref={gridRef} className="reveal">
|
|
||||||
<div className="terminal-label mb-4 text-xs">// featured</div>
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
|
||||||
{featured.map((project, i) => (
|
|
||||||
<ProjectCard
|
|
||||||
key={project.name}
|
|
||||||
project={project}
|
|
||||||
delay={i * 80}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Rest of projects (smaller grid) */}
|
|
||||||
<div className="terminal-label mb-4 text-xs mt-8">
|
|
||||||
// more projects
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
|
||||||
{rest.map((project, i) => (
|
|
||||||
<ProjectCard
|
|
||||||
key={project.name}
|
|
||||||
project={project}
|
|
||||||
delay={i * 60}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
/*
|
|
||||||
* SKILLS SECTION — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Flowing chip layout with category grouping
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
CpuIcon,
|
|
||||||
PaletteIcon,
|
|
||||||
RocketIcon,
|
|
||||||
ShieldCheckIcon,
|
|
||||||
TerminalIcon,
|
|
||||||
ZapIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
function useReveal(threshold = 0.1) {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
const el = ref.current;
|
|
||||||
if (!el) return;
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
el.classList.add("visible");
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold },
|
|
||||||
);
|
|
||||||
observer.observe(el);
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, [threshold]);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CODE_VISUAL =
|
|
||||||
"https://d2xsxph8kpxj0f.cloudfront.net/310519663439135967/RkRL4wd7YzNoLyaN7suWbR/code-visual-96fdFEYLcFy2ct3UAAmiG6.webp";
|
|
||||||
|
|
||||||
interface SkillCategory {
|
|
||||||
label: string;
|
|
||||||
terminalLabel: string;
|
|
||||||
skills: { name: string; level?: "expert" | "proficient" | "learning" }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const skillCategories: SkillCategory[] = [
|
|
||||||
{
|
|
||||||
label: "Languages",
|
|
||||||
terminalLabel: "languages/",
|
|
||||||
skills: [
|
|
||||||
{ name: "Rust", level: "expert" },
|
|
||||||
{ name: "TypeScript", level: "expert" },
|
|
||||||
{ name: "JavaScript", level: "expert" },
|
|
||||||
{ name: "Java", level: "proficient" },
|
|
||||||
{ name: "Python", level: "proficient" },
|
|
||||||
{ name: "C#", level: "proficient" },
|
|
||||||
{ name: "Shell / Bash", level: "proficient" },
|
|
||||||
{ name: "Go", level: "learning" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Frontend",
|
|
||||||
terminalLabel: "frontend/",
|
|
||||||
skills: [
|
|
||||||
{ name: "React", level: "expert" },
|
|
||||||
{ name: "Next.js", level: "expert" },
|
|
||||||
{ name: "Tailwind CSS", level: "expert" },
|
|
||||||
{ name: "Framer Motion", level: "proficient" },
|
|
||||||
{ name: "WebSockets", level: "expert" },
|
|
||||||
{ name: "Vite", level: "expert" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Backend & Systems",
|
|
||||||
terminalLabel: "backend/",
|
|
||||||
skills: [
|
|
||||||
{ name: "Node.js", level: "expert" },
|
|
||||||
{ name: "Express", level: "expert" },
|
|
||||||
{ name: "Linux", level: "expert" },
|
|
||||||
{ name: "Docker", level: "proficient" },
|
|
||||||
{ name: "TUI / CLI", level: "expert" },
|
|
||||||
{ name: "Low-level Systems", level: "expert" },
|
|
||||||
{ name: "WebAssembly", level: "proficient" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Tools & Platforms",
|
|
||||||
terminalLabel: "tools/",
|
|
||||||
skills: [
|
|
||||||
{ name: "Git / GitHub", level: "expert" },
|
|
||||||
{ name: "Kali Linux", level: "proficient" },
|
|
||||||
{ name: "Neovim", level: "proficient" },
|
|
||||||
{ name: "PostgreSQL", level: "proficient" },
|
|
||||||
{ name: "Redis", level: "proficient" },
|
|
||||||
{ name: "Figma", level: "proficient" },
|
|
||||||
{ name: "Solana / Web3", level: "proficient" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const levelColors: Record<string, string> = {
|
|
||||||
expert: "rgba(255,255,255,0.12)",
|
|
||||||
proficient: "rgba(255,255,255,0.06)",
|
|
||||||
learning: "rgba(255,255,255,0.03)",
|
|
||||||
};
|
|
||||||
|
|
||||||
const levelBorders: Record<string, string> = {
|
|
||||||
expert: "rgba(255,255,255,0.2)",
|
|
||||||
proficient: "rgba(255,255,255,0.1)",
|
|
||||||
learning: "rgba(255,255,255,0.06)",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SkillsSection() {
|
|
||||||
const headerRef = useReveal();
|
|
||||||
const contentRef = useReveal(0.05);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
id="skills"
|
|
||||||
className="py-32 relative"
|
|
||||||
style={{ background: "#080808" }}
|
|
||||||
>
|
|
||||||
<div className="absolute top-0 left-0 right-0 h-px bg-linear-to-r from-transparent via-white/10 to-transparent" />
|
|
||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div ref={headerRef} className="reveal mb-16">
|
|
||||||
<div className="terminal-label mb-4">> cat skills.json</div>
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-4">
|
|
||||||
<h2 className="section-heading text-4xl sm:text-5xl text-white leading-tight">
|
|
||||||
Tech I Work <span className="text-white/40">With</span>
|
|
||||||
</h2>
|
|
||||||
<div className="flex items-center gap-4 terminal-label text-xs">
|
|
||||||
<span className="flex items-center gap-1.5">
|
|
||||||
<span className="w-2 h-2 rounded-full inline-block bg-white/50" />
|
|
||||||
Expert
|
|
||||||
</span>
|
|
||||||
<span className="flex items-center gap-1.5">
|
|
||||||
<span className="w-2 h-2 rounded-full inline-block bg-white/25" />
|
|
||||||
Proficient
|
|
||||||
</span>
|
|
||||||
<span className="flex items-center gap-1.5">
|
|
||||||
<span className="w-2 h-2 rounded-full inline-block bg-white/10" />
|
|
||||||
Learning
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ref={contentRef} className="reveal">
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
||||||
{/* Left: skill categories */}
|
|
||||||
<div className="lg:col-span-2 space-y-8">
|
|
||||||
{skillCategories.map((cat) => (
|
|
||||||
<div key={cat.label}>
|
|
||||||
<div className="flex items-center gap-3 mb-4">
|
|
||||||
<span className="terminal-label text-xs">
|
|
||||||
{cat.terminalLabel}
|
|
||||||
</span>
|
|
||||||
<div className="flex-1 h-px bg-white/5" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{cat.skills.map((skill) => (
|
|
||||||
<span
|
|
||||||
key={skill.name}
|
|
||||||
className="skill-chip"
|
|
||||||
style={{
|
|
||||||
background: skill.level
|
|
||||||
? levelColors[skill.level]
|
|
||||||
: undefined,
|
|
||||||
borderColor: skill.level
|
|
||||||
? levelBorders[skill.level]
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{skill.name}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right: code visual + interests */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
{/* Code visual */}
|
|
||||||
<div className="glass rounded-2xl overflow-hidden">
|
|
||||||
<img
|
|
||||||
src={CODE_VISUAL}
|
|
||||||
alt="Code visualization"
|
|
||||||
className="w-full h-48 object-cover opacity-70"
|
|
||||||
/>
|
|
||||||
<div className="p-4">
|
|
||||||
<div className="terminal-label text-xs mb-1">
|
|
||||||
$ git log --oneline
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="text-white/50 text-xs leading-relaxed"
|
|
||||||
style={{ fontFamily: "'JetBrains Mono', monospace" }}
|
|
||||||
>
|
|
||||||
<div>214 commits this month</div>
|
|
||||||
<div>2,670 contributions/year</div>
|
|
||||||
<div>35+ repositories</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Interests */}
|
|
||||||
<div className="glass rounded-2xl p-5">
|
|
||||||
<div className="terminal-label text-xs mb-4">
|
|
||||||
> interests/
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
icon: <ShieldCheckIcon size={20} />,
|
|
||||||
label: "Hacking & Security",
|
|
||||||
},
|
|
||||||
{ icon: <CpuIcon size={20} />, label: "Low-level Systems" },
|
|
||||||
{ icon: <PaletteIcon size={20} />, label: "UI/UX Design" },
|
|
||||||
{
|
|
||||||
icon: <RocketIcon size={20} />,
|
|
||||||
label: "SaaS & Startups",
|
|
||||||
},
|
|
||||||
{ icon: <ZapIcon size={20} />, label: "Rust Ecosystem" },
|
|
||||||
{
|
|
||||||
icon: <TerminalIcon size={20} />,
|
|
||||||
label: "Linux & Open Source",
|
|
||||||
},
|
|
||||||
].map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.label}
|
|
||||||
className="flex items-center gap-2 text-sm text-white/60"
|
|
||||||
style={{ fontFamily: "'DM Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
<span className="text-base">{item.icon}</span>
|
|
||||||
{item.label}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
||||||
destructive:
|
|
||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
||||||
outline:
|
|
||||||
"border bg-transparent shadow-xs hover:bg-accent dark:bg-transparent dark:border-input dark:hover:bg-input/50",
|
|
||||||
secondary:
|
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
||||||
ghost:
|
|
||||||
"hover:bg-accent dark:hover:bg-accent/50",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
||||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
||||||
icon: "size-9",
|
|
||||||
"icon-sm": "size-8",
|
|
||||||
"icon-lg": "size-10",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function Button({
|
|
||||||
className,
|
|
||||||
variant,
|
|
||||||
size,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"button"> &
|
|
||||||
VariantProps<typeof buttonVariants> & {
|
|
||||||
asChild?: boolean;
|
|
||||||
}) {
|
|
||||||
const Comp = asChild ? Slot : "button";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
data-slot="button"
|
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Button, buttonVariants };
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card"
|
|
||||||
className={cn(
|
|
||||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-header"
|
|
||||||
className={cn(
|
|
||||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-title"
|
|
||||||
className={cn("leading-none font-semibold", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-description"
|
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-action"
|
|
||||||
className={cn(
|
|
||||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-content"
|
|
||||||
className={cn("px-6", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-footer"
|
|
||||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
CardFooter,
|
|
||||||
CardTitle,
|
|
||||||
CardAction,
|
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { useTheme } from "next-themes";
|
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
|
||||||
const { theme = "system" } = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sonner
|
|
||||||
theme={theme as ToasterProps["theme"]}
|
|
||||||
className="toaster group"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--normal-bg": "var(--popover)",
|
|
||||||
"--normal-text": "var(--popover-foreground)",
|
|
||||||
"--normal-border": "var(--border)",
|
|
||||||
} as React.CSSProperties
|
|
||||||
}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Toaster };
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
function TooltipProvider({
|
|
||||||
delayDuration = 0,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
||||||
return (
|
|
||||||
<TooltipPrimitive.Provider
|
|
||||||
data-slot="tooltip-provider"
|
|
||||||
delayDuration={delayDuration}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Tooltip({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
||||||
return (
|
|
||||||
<TooltipProvider>
|
|
||||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TooltipTrigger({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
||||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TooltipContent({
|
|
||||||
className,
|
|
||||||
sideOffset = 0,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
||||||
return (
|
|
||||||
<TooltipPrimitive.Portal>
|
|
||||||
<TooltipPrimitive.Content
|
|
||||||
data-slot="tooltip-content"
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px]" />
|
|
||||||
</TooltipPrimitive.Content>
|
|
||||||
</TooltipPrimitive.Portal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export { COOKIE_NAME, ONE_YEAR_MS } from "@shared/const";
|
|
||||||
|
|
||||||
// Generate login URL at runtime so redirect URI reflects the current origin.
|
|
||||||
export const getLoginUrl = () => {
|
|
||||||
const oauthPortalUrl = import.meta.env.VITE_OAUTH_PORTAL_URL;
|
|
||||||
const appId = import.meta.env.VITE_APP_ID;
|
|
||||||
const redirectUri = `${window.location.origin}/api/oauth/callback`;
|
|
||||||
const state = btoa(redirectUri);
|
|
||||||
|
|
||||||
const url = new URL(`${oauthPortalUrl}/app-auth`);
|
|
||||||
url.searchParams.set("appId", appId);
|
|
||||||
url.searchParams.set("redirectUri", redirectUri);
|
|
||||||
url.searchParams.set("state", state);
|
|
||||||
url.searchParams.set("type", "signIn");
|
|
||||||
|
|
||||||
return url.toString();
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
|
||||||
|
|
||||||
type Theme = "light" | "dark";
|
|
||||||
|
|
||||||
interface ThemeContextType {
|
|
||||||
theme: Theme;
|
|
||||||
toggleTheme?: () => void;
|
|
||||||
switchable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
interface ThemeProviderProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
defaultTheme?: Theme;
|
|
||||||
switchable?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeProvider({
|
|
||||||
children,
|
|
||||||
defaultTheme = "light",
|
|
||||||
switchable = false,
|
|
||||||
}: ThemeProviderProps) {
|
|
||||||
const [theme, setTheme] = useState<Theme>(() => {
|
|
||||||
if (switchable) {
|
|
||||||
const stored = localStorage.getItem("theme");
|
|
||||||
return (stored as Theme) || defaultTheme;
|
|
||||||
}
|
|
||||||
return defaultTheme;
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const root = document.documentElement;
|
|
||||||
if (theme === "dark") {
|
|
||||||
root.classList.add("dark");
|
|
||||||
} else {
|
|
||||||
root.classList.remove("dark");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switchable) {
|
|
||||||
localStorage.setItem("theme", theme);
|
|
||||||
}
|
|
||||||
}, [theme, switchable]);
|
|
||||||
|
|
||||||
const toggleTheme = switchable
|
|
||||||
? () => {
|
|
||||||
setTheme(prev => (prev === "light" ? "dark" : "light"));
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeContext.Provider value={{ theme, toggleTheme, switchable }}>
|
|
||||||
{children}
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTheme() {
|
|
||||||
const context = useContext(ThemeContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error("useTheme must be used within ThemeProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
const MOBILE_BREAKPOINT = 768;
|
|
||||||
|
|
||||||
export function useIsMobile() {
|
|
||||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
||||||
const onChange = () => {
|
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
||||||
};
|
|
||||||
mql.addEventListener("change", onChange);
|
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
||||||
return () => mql.removeEventListener("change", onChange);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return !!isMobile;
|
|
||||||
}
|
|
||||||
-280
@@ -1,280 +0,0 @@
|
|||||||
/*
|
|
||||||
* DESIGN SYSTEM — selimaj.dev
|
|
||||||
* Philosophy: Glassmorphic Terminal
|
|
||||||
* Background: near-black (#080808), Glass cards: rgba(255,255,255,0.04) + blur
|
|
||||||
* Typography: Syne (headings) + JetBrains Mono (labels/code) + Geist (body)
|
|
||||||
* Accent: White only — no color distractions
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import "tailwindcss";
|
|
||||||
@import "tw-animate-css";
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-destructive-foreground: var(--destructive-foreground);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--radius: 0.75rem;
|
|
||||||
--background: oklch(0.06 0 0);
|
|
||||||
--foreground: oklch(0.95 0 0);
|
|
||||||
--card: oklch(0.09 0 0);
|
|
||||||
--card-foreground: oklch(0.95 0 0);
|
|
||||||
--popover: oklch(0.09 0 0);
|
|
||||||
--popover-foreground: oklch(0.95 0 0);
|
|
||||||
--primary: oklch(0.95 0 0);
|
|
||||||
--primary-foreground: oklch(0.06 0 0);
|
|
||||||
--secondary: oklch(0.12 0 0);
|
|
||||||
--secondary-foreground: oklch(0.85 0 0);
|
|
||||||
--muted: oklch(0.14 0 0);
|
|
||||||
--muted-foreground: oklch(0.55 0 0);
|
|
||||||
--accent: oklch(0.14 0 0);
|
|
||||||
--accent-foreground: oklch(0.95 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--destructive-foreground: oklch(0.985 0 0);
|
|
||||||
--border: oklch(1 0 0 / 8%);
|
|
||||||
--input: oklch(1 0 0 / 10%);
|
|
||||||
--ring: oklch(0.95 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border outline-ring/50;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
font-family: 'Geist', 'DM Sans', sans-serif;
|
|
||||||
background-color: #080808;
|
|
||||||
color: #f0f0f0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
button:not(:disabled),
|
|
||||||
[role="button"]:not([aria-disabled="true"]),
|
|
||||||
[type="button"]:not(:disabled),
|
|
||||||
[type="submit"]:not(:disabled),
|
|
||||||
a[href],
|
|
||||||
select:not(:disabled) {
|
|
||||||
@apply cursor-pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer components {
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
min-height: 0;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
.container {
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
padding-right: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.container {
|
|
||||||
padding-left: 2rem;
|
|
||||||
padding-right: 2rem;
|
|
||||||
max-width: 1280px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Glass card utility */
|
|
||||||
.glass {
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
-webkit-backdrop-filter: blur(12px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-strong {
|
|
||||||
background: rgba(255, 255, 255, 0.07);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navbar glass */
|
|
||||||
.navbar-glass {
|
|
||||||
background: rgba(8, 8, 8, 0.6);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-glass-solid {
|
|
||||||
background: rgba(8, 8, 8, 0.92);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Terminal label style */
|
|
||||||
.terminal-label {
|
|
||||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: rgba(255, 255, 255, 0.45);
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section heading style */
|
|
||||||
.section-heading {
|
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Subtle glow on hover for cards */
|
|
||||||
.card-hover {
|
|
||||||
transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
|
|
||||||
}
|
|
||||||
.card-hover:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
|
||||||
box-shadow: 0 0 30px rgba(255, 255, 255, 0.04), 0 8px 32px rgba(0,0,0,0.4);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animated gradient text */
|
|
||||||
.gradient-text {
|
|
||||||
background: linear-gradient(135deg, #ffffff 0%, rgba(255,255,255,0.6) 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Noise overlay */
|
|
||||||
.noise-overlay::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scroll reveal animation */
|
|
||||||
.reveal {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(24px);
|
|
||||||
transition: opacity 0.6s ease, transform 0.6s ease;
|
|
||||||
}
|
|
||||||
.reveal.visible {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skill chip */
|
|
||||||
.skill-chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
padding: 0.375rem 0.875rem;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 9999px;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
|
||||||
}
|
|
||||||
.skill-chip:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cursor blink */
|
|
||||||
@keyframes blink {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0; }
|
|
||||||
}
|
|
||||||
.cursor-blink {
|
|
||||||
animation: blink 1s step-end infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Floating animation */
|
|
||||||
@keyframes float {
|
|
||||||
0%, 100% { transform: translateY(0px); }
|
|
||||||
50% { transform: translateY(-8px); }
|
|
||||||
}
|
|
||||||
.float-anim {
|
|
||||||
animation: float 4s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fade in up */
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
from { opacity: 0; transform: translateY(30px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
.fade-in-up {
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeInUp 0.7s ease forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stagger delays */
|
|
||||||
.delay-100 { animation-delay: 0.1s; }
|
|
||||||
.delay-200 { animation-delay: 0.2s; }
|
|
||||||
.delay-300 { animation-delay: 0.3s; }
|
|
||||||
.delay-400 { animation-delay: 0.4s; }
|
|
||||||
.delay-500 { animation-delay: 0.5s; }
|
|
||||||
.delay-600 { animation-delay: 0.6s; }
|
|
||||||
.delay-700 { animation-delay: 0.7s; }
|
|
||||||
.delay-800 { animation-delay: 0.8s; }
|
|
||||||
|
|
||||||
/* White dot separator */
|
|
||||||
.dot-sep::after {
|
|
||||||
content: '·';
|
|
||||||
margin: 0 0.5rem;
|
|
||||||
color: rgba(255,255,255,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Project language badge */
|
|
||||||
.lang-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: rgba(255,255,255,0.5);
|
|
||||||
}
|
|
||||||
.lang-dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { clsx, type ClassValue } from "clsx";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs));
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { createRoot } from "react-dom/client";
|
|
||||||
import App from "./App";
|
|
||||||
import "./index.css";
|
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(<App />);
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* HOME PAGE — selimaj.dev
|
|
||||||
* Design: Glassmorphic Terminal
|
|
||||||
* Assembles all sections: Navbar, Hero, About, Projects, Skills, Contact, Footer
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Navbar from "@/components/Navbar";
|
|
||||||
import HeroSection from "@/components/HeroSection";
|
|
||||||
import AboutSection from "@/components/AboutSection";
|
|
||||||
import ProjectsSection from "@/components/ProjectsSection";
|
|
||||||
import SkillsSection from "@/components/SkillsSection";
|
|
||||||
import ContactSection from "@/components/ContactSection";
|
|
||||||
import Footer from "@/components/Footer";
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="min-h-screen"
|
|
||||||
style={{ background: "#080808", color: "#f0f0f0" }}
|
|
||||||
>
|
|
||||||
<Navbar />
|
|
||||||
<HeroSection />
|
|
||||||
<AboutSection />
|
|
||||||
<ProjectsSection />
|
|
||||||
<SkillsSection />
|
|
||||||
<ContactSection />
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
|
||||||
import { AlertCircle, Home } from "lucide-react";
|
|
||||||
import { useLocation } from "wouter";
|
|
||||||
|
|
||||||
export default function NotFound() {
|
|
||||||
const [, setLocation] = useLocation();
|
|
||||||
|
|
||||||
const handleGoHome = () => setLocation("/");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen w-full flex items-center justify-center bg-background">
|
|
||||||
<Card className="w-full max-w-lg mx-4 shadow-lg border-none">
|
|
||||||
<CardContent className="pt-8 pb-8 text-center">
|
|
||||||
<div className="flex justify-center mb-6">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="absolute inset-0 rounded-full animate-pulse bg-destructive/20" />
|
|
||||||
<AlertCircle className="relative h-16 w-16 text-destructive" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 className="text-4xl font-bold text-foreground mb-2">404</h1>
|
|
||||||
|
|
||||||
<h2 className="text-xl font-semibold text-muted-foreground mb-4">
|
|
||||||
Page Not Found
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground mb-8 leading-relaxed">
|
|
||||||
Sorry, the page you are looking for doesn't exist.
|
|
||||||
<br />
|
|
||||||
It may have been moved or deleted.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
|
||||||
<Button variant="default" size="lg" onClick={handleGoHome}>
|
|
||||||
<Home className="w-4 h-4 mr-2" />
|
|
||||||
Go Home
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
const plugin = require('tailwindcss/plugin');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
darkMode: 'class', // supports .dark class
|
|
||||||
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
background: 'var(--color-background)',
|
|
||||||
foreground: 'var(--color-foreground)',
|
|
||||||
card: 'var(--color-card)',
|
|
||||||
'card-foreground': 'var(--color-card-foreground)',
|
|
||||||
popover: 'var(--color-popover)',
|
|
||||||
'popover-foreground': 'var(--color-popover-foreground)',
|
|
||||||
primary: 'var(--color-primary)',
|
|
||||||
'primary-foreground': 'var(--color-primary-foreground)',
|
|
||||||
secondary: 'var(--color-secondary)',
|
|
||||||
'secondary-foreground': 'var(--color-secondary-foreground)',
|
|
||||||
muted: 'var(--color-muted)',
|
|
||||||
'muted-foreground': 'var(--color-muted-foreground)',
|
|
||||||
accent: 'var(--color-accent)',
|
|
||||||
'accent-foreground': 'var(--color-accent-foreground)',
|
|
||||||
destructive: 'var(--color-destructive)',
|
|
||||||
'destructive-foreground': 'var(--color-destructive-foreground)',
|
|
||||||
border: 'var(--color-border)',
|
|
||||||
input: 'var(--color-input)',
|
|
||||||
ring: 'var(--color-ring)',
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
sm: 'var(--radius-sm)',
|
|
||||||
md: 'var(--radius-md)',
|
|
||||||
lg: 'var(--radius-lg)',
|
|
||||||
xl: 'var(--radius-xl)',
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
heading: ['Syne', 'sans-serif'],
|
|
||||||
body: ['Geist', 'DM Sans', 'sans-serif'],
|
|
||||||
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
|
||||||
},
|
|
||||||
backdropBlur: {
|
|
||||||
sm: '12px',
|
|
||||||
md: '20px',
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
blink: {
|
|
||||||
'0%, 100%': { opacity: '1' },
|
|
||||||
'50%': { opacity: '0' },
|
|
||||||
},
|
|
||||||
float: {
|
|
||||||
'0%, 100%': { transform: 'translateY(0px)' },
|
|
||||||
'50%': { transform: 'translateY(-8px)' },
|
|
||||||
},
|
|
||||||
fadeInUp: {
|
|
||||||
'0%': { opacity: '0', transform: 'translateY(30px)' },
|
|
||||||
'100%': { opacity: '1', transform: 'translateY(0)' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
blink: 'blink 1s step-end infinite',
|
|
||||||
float: 'float 4s ease-in-out infinite',
|
|
||||||
fadeInUp: 'fadeInUp 0.7s ease forwards',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
require('tw-animate-css'),
|
|
||||||
plugin(function({ addUtilities }) {
|
|
||||||
addUtilities({
|
|
||||||
'.glass': {
|
|
||||||
background: 'rgba(255, 255, 255, 0.04)',
|
|
||||||
'backdrop-filter': 'blur(12px)',
|
|
||||||
'-webkit-backdrop-filter': 'blur(12px)',
|
|
||||||
border: '1px solid rgba(255, 255, 255, 0.08)',
|
|
||||||
},
|
|
||||||
'.glass-strong': {
|
|
||||||
background: 'rgba(255, 255, 255, 0.07)',
|
|
||||||
'backdrop-filter': 'blur(20px)',
|
|
||||||
'-webkit-backdrop-filter': 'blur(20px)',
|
|
||||||
border: '1px solid rgba(255, 255, 255, 0.12)',
|
|
||||||
},
|
|
||||||
'.navbar-glass': {
|
|
||||||
background: 'rgba(8, 8, 8, 0.6)',
|
|
||||||
'backdrop-filter': 'blur(20px)',
|
|
||||||
'-webkit-backdrop-filter': 'blur(20px)',
|
|
||||||
'border-bottom': '1px solid rgba(255, 255, 255, 0.06)',
|
|
||||||
},
|
|
||||||
'.navbar-glass-solid': {
|
|
||||||
background: 'rgba(8, 8, 8, 0.92)',
|
|
||||||
'backdrop-filter': 'blur(20px)',
|
|
||||||
'-webkit-backdrop-filter': 'blur(20px)',
|
|
||||||
'border-bottom': '1px solid rgba(255, 255, 255, 0.08)',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
"target": "ES2023",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["ES2023", "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,
|
|
||||||
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"files": [],
|
|
||||||
"references": [
|
|
||||||
{ "path": "./tsconfig.app.json" },
|
|
||||||
{ "path": "./tsconfig.node.json" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"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,
|
|
||||||
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { defineConfig } from 'vite'
|
|
||||||
import react from '@vitejs/plugin-react'
|
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react(), tailwindcss()],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
"@": path.resolve(__dirname, "./src"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user