Merge pull request #2 from kleo-dev/monochrome-dvd

Monochrome dvd
This commit is contained in:
Klesti Selimaj
2025-10-21 22:44:39 +02:00
committed by GitHub
8 changed files with 198 additions and 234 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

+34
View File
@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest) {
const username = "kleo-dev";
const token = process.env.GITHUB_TOKEN;
const query = `
query {
user(login: "${username}") {
contributionsCollection {
contributionCalendar {
totalContributions
}
}
}
}
`;
const response = await fetch("https://api.github.com/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ query }),
});
const data = await response.json();
const total =
data.data.user.contributionsCollection.contributionCalendar
.totalContributions;
return NextResponse.json({ total }, { status: 200 });
}
+1 -3
View File
@@ -17,9 +17,7 @@ export default function RootLayout({
<html lang="en"> <html lang="en">
<body className="antialiased bg-black"> <body className="antialiased bg-black">
<Background> <Background>
<div className="relative h-screen overflow-y-scroll"> <div className="relative h-screen overflow-y-scroll">{children}</div>
{children}
</div>
</Background> </Background>
</body> </body>
</html> </html>
+57 -17
View File
@@ -1,3 +1,5 @@
"use client";
import PinnedRepos from "@/components/PinnedRepos"; import PinnedRepos from "@/components/PinnedRepos";
import Skill from "@/components/Skill"; import Skill from "@/components/Skill";
import { import {
@@ -12,20 +14,33 @@ import {
} from "@fortawesome/free-brands-svg-icons"; } from "@fortawesome/free-brands-svg-icons";
import { faJava } from "@fortawesome/free-brands-svg-icons/faJava"; import { faJava } from "@fortawesome/free-brands-svg-icons/faJava";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import GitHubCalendar from "react-github-calendar"; import GitHubCalendar from "react-github-calendar";
export default function App() { export default function App() {
const [contribs, setContribs] = useState<number>();
useEffect(() => {
const fetchContributions = async () => {
const res = await fetch("/api/contribs");
const data = await res.json();
console.log("Total contributions:", data.total);
setContribs(data.total || undefined);
};
fetchContributions();
}, []);
return ( return (
<div> <div>
<div className="content-center w-full lg:mx-auto pt-5 relative z-20 pb-10 animate-fadeIn"> <div className="content-center w-full lg:mx-auto pt-5 relative z-20 pb-5 animate-fadeIn">
<div className="w-full max-w-screen-lg mx-auto"> <div className="w-full max-w-screen-lg mx-auto">
<div className="px-8 md:px-10 lg:px-15 xl:px-24"> <div className="px-8 md:px-10 lg:px-15 xl:px-24">
<h1 className="py-2 text-gray-300">Hey, I'm Klesti! 👋🏻</h1> <h1 className="py-2 text-gray-300">Hey, I'm Klesti! 👋🏻</h1>
<p className="font-semibold text-gray-300"> <p className="font-semibold text-gray-300">
A 15 year old Software Engineer from Tirane, Albania, with 6 years A 15 year old Software Engineer from Tirane, Albania, with 6 years
of experience, specializing in full-stack web development, desktop of experience, specializing in full-stack web development, desktop
applications, TUI apps, and hacking. I also have a deep interest in applications, TUI apps, and hacking. I also have a deep interest
low-level programming. in low-level programming.
</p> </p>
{/* Socials and skill */} {/* Socials and skill */}
@@ -35,8 +50,7 @@ export default function App() {
<a href="https://github.com/kleo-dev/" target="blank_"> <a href="https://github.com/kleo-dev/" target="blank_">
<FontAwesomeIcon <FontAwesomeIcon
icon={faGithub} icon={faGithub}
width={36} className="text-slate-300 size-[2.3rem]"
className="text-red"
/> />
</a> </a>
<a <a
@@ -45,15 +59,16 @@ export default function App() {
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faLinkedin} icon={faLinkedin}
width={36} className="text-slate-300 size-[2.3rem]"
className="text-red"
/> />
</a> </a>
<a href="https://www.instagram.com/kleo.dev/" target="blank_"> <a
href="https://www.instagram.com/selimaj.dev/"
target="blank_"
>
<FontAwesomeIcon <FontAwesomeIcon
icon={faInstagram} icon={faInstagram}
width={36} className="text-slate-300 size-[2.3rem]"
className="text-red"
/> />
</a> </a>
</div> </div>
@@ -70,17 +85,42 @@ export default function App() {
</div> </div>
</div> </div>
</div> </div>
<h1 className="text-red-400">Pinned Projects</h1> <h1 className="text-slate-300">Pinned Projects</h1>
<section className=""> <section className="">
<PinnedRepos /> <PinnedRepos />
</section> </section>
<section className="mt-8"> <h2 className="text-slate-300 mt-7">
<GitHubCalendar username="kleo-dev" theme={{ {contribs || "Loading.."} contributions in the last year
light: ["#1a000f", "#5c001d", "#9f0030", "#d10045", "#f5304c"], </h2>
dark: ["#1a000f", "#5c001d", "#9f0030", "#d10045", "#f5304c"], <section className="mt-4">
}} /> <GitHubCalendar
hideTotalCount
username="kleo-dev"
theme={{
light: [
"rgba(255, 255, 255, 0.1)",
"rgba(255, 255, 255, 0.3)",
"rgba(255, 255, 255, 0.5)",
"rgba(255, 255, 255, 0.7)",
"rgba(255, 255, 255, 1)",
],
dark: [
"rgba(255, 255, 255, 0.1)",
"rgba(255, 255, 255, 0.3)",
"rgba(255, 255, 255, 0.5)",
"rgba(255, 255, 255, 0.7)",
"rgba(255, 255, 255, 1)",
],
}}
/>
</section> </section>
<footer className="mt-8 border-t pt-3">
If you see this i ❤️ you
<p className="text-slate-300 mx-auto w-max mt-2">
© 2025 Klesti. All rights reserved.
</p>
</footer>
</div> </div>
</div> </div>
</div> </div>
+62 -171
View File
@@ -1,4 +1,4 @@
'use client'; "use client";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
@@ -7,183 +7,74 @@ export default function Background({
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const canvasRef = useRef<HTMLCanvasElement | null>(null); const [pos, setPos] = useState({ x: 30, y: 100 });
const animationRef = useRef<number>(0); const [vel, setVel] = useState({ x: 1, y: 1 });
const particlesRef = useRef<Particle[]>([]);
const [dimensions, setDimensions] = useState<{ width: number; height: number }>({
width: 0,
height: 0,
});
// Particle configuration
const config = {
particleCount: 400,
maxDistance: 120,
particleSpeed: 0.5,
particleSize: 2,
lineOpacity: 0.6,
particleOpacity: 0.8,
colors: {
particles: "#f5304c", // blue-400
connections: "#f5304c", // blue-500
},
};
// Particle class
class Particle {
x: number;
y: number;
vx: number;
vy: number;
size: number;
constructor(width: number, height: number) {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.vx = (Math.random() - 0.5) * config.particleSpeed;
this.vy = (Math.random() - 0.5) * config.particleSpeed;
this.size = config.particleSize + Math.random() * 2;
}
update(width: number, height: number) {
this.x += this.vx;
this.y += this.vy;
// Bounce off edges
if (this.x < 0 || this.x > width) {
this.vx = -this.vx;
this.x = Math.max(0, Math.min(width, this.x));
}
if (this.y < 0 || this.y > height) {
this.vy = -this.vy;
this.y = Math.max(0, Math.min(height, this.y));
}
}
draw(ctx: CanvasRenderingContext2D) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = `${config.colors.particles}${Math.round(
config.particleOpacity * 255
)
.toString(16)
.padStart(2, "0")}`;
ctx.fill();
}
}
// Initialize particles
const initParticles = (width: number, height: number) => {
particlesRef.current = [];
for (let i = 0; i < config.particleCount; i++) {
particlesRef.current.push(new Particle(width, height));
}
};
// Draw connections between nearby particles
const drawConnections = (ctx: CanvasRenderingContext2D, particles: Particle[]) => {
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < config.maxDistance) {
const opacity = 1 - distance / config.maxDistance;
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `${config.colors.connections}${Math.round(
opacity * config.lineOpacity * 255
)
.toString(16)
.padStart(2, "0")}`;
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
};
// Animation loop
const animate = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const { width, height } = dimensions;
// Clear canvas
ctx.clearRect(0, 0, width, height);
// Update and draw particles
particlesRef.current.forEach((particle) => {
particle.update(width, height);
particle.draw(ctx);
});
// Draw connections
drawConnections(ctx, particlesRef.current);
animationRef.current = requestAnimationFrame(animate);
};
// Handle window resize
const handleResize = () => {
const canvas = canvasRef.current;
if (!canvas || !canvas.parentElement) return;
const rect = canvas.parentElement.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const ctx = canvas.getContext("2d");
if (ctx) {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset scaling before re-scaling
ctx.scale(dpr, dpr);
}
setDimensions({ width, height });
};
useEffect(() => { useEffect(() => {
handleResize(); let frame: number;
window.addEventListener("resize", handleResize); let x = pos.x;
let y = pos.y;
let vx = vel.x;
let vy = vel.y;
return () => { const speed = 0.5;
window.removeEventListener("resize", handleResize); const size = 100;
if (animationRef.current) {
cancelAnimationFrame(animationRef.current); // helper for collision detection
} const detectCollision = (
nextX: number,
nextY: number,
w: number,
h: number
) => {
let collidedX = false;
let collidedY = false;
// Left or Right wall
if (nextX <= 0 || nextX + size >= w) collidedX = true;
// Top or Bottom wall
if (nextY <= 0 || nextY + size >= h) collidedY = true;
return { collidedX, collidedY };
}; };
const animate = () => {
const w = window.innerWidth;
const h = window.innerHeight;
const nextX = x + vx * speed;
const nextY = y + vy * speed;
const { collidedX, collidedY } = detectCollision(nextX, nextY, w, h);
if (collidedX) vx *= -1;
if (collidedY) vy *= -1;
x += vx * speed;
y += vy * speed;
setPos({ x, y });
setVel({ x: vx, y: vy });
frame = requestAnimationFrame(animate);
};
frame = requestAnimationFrame(animate);
return () => cancelAnimationFrame(frame);
}, []); }, []);
useEffect(() => {
if (dimensions.width && dimensions.height) {
initParticles(dimensions.width, dimensions.height);
animate();
}
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [dimensions]);
return ( return (
<div className="relative w-full min-h-screen overflow-hidden bg-black"> <div className="relative w-full min-h-screen overflow-hidden bg-black">
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" /> <img
style={{
{children} transform: `translate(${pos.x}px, ${pos.y}px)`,
}}
src="/logo.png"
alt="Logo"
className="size-24 rounded-full border-slate-500 border"
/>
<div className="relative z-10">{children}</div>
</div> </div>
); );
}; }
+4 -4
View File
@@ -13,8 +13,8 @@ export default function Navbar() {
<Link <Link
href="/" href="/"
className={ className={
"hover:text-red transition-colors duration-300" + "hover:slate-200 transition-colors duration-300" +
(pathname === "/" ? " text-red" : "") (pathname === "/" ? " slate-200" : "")
} }
> >
Home Home
@@ -22,8 +22,8 @@ export default function Navbar() {
<Link <Link
href="/pricing" href="/pricing"
className={ className={
"hover:text-red transition-colors duration-300" + "hover:slate-200 transition-colors duration-300" +
(pathname === "/pricing" ? " text-red" : "") (pathname === "/pricing" ? " slate-200" : "")
} }
> >
Pricing Pricing
+38 -36
View File
@@ -1,4 +1,4 @@
'use client'; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -31,7 +31,7 @@ export function Repo({
rounded-xl rounded-xl
h-full h-full
border border
border-red border-slate-300
hover:shadow-md hover:shadow-md
transition-shadow transition-shadow
duration-300 duration-300
@@ -41,7 +41,7 @@ export function Repo({
<div className="w-full"> <div className="w-full">
<div className="rounded-xl border text-card-foreground shadow h-full bg-transparent border-none flex flex-col"> <div className="rounded-xl border text-card-foreground shadow h-full bg-transparent border-none flex flex-col">
<div className="flex flex-col space-y-1.5 p-6 flex-grow"> <div className="flex flex-col space-y-1.5 p-6 flex-grow">
<h3 className="tracking-tight text-base sm:text-lg font-semibold text-red-400"> <h3 className="tracking-tight text-base sm:text-lg font-semibold text-slate-300">
<a <a
href={href} href={href}
target="_blank" target="_blank"
@@ -73,7 +73,7 @@ export function Repo({
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
className="lucide lucide-star text-yellow-500 h-3 w-3 sm:h-4 sm:w-4" className="lucide lucide-star text-gray-300 h-3 w-3 sm:h-4 sm:w-4"
> >
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /> <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
</svg> </svg>
@@ -106,28 +106,28 @@ export function Repo({
</div> </div>
{lang && ( {lang && (
<div <div
className="inline-flex items-center rounded-md border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground text-xs bg-red-600/10 border-red-600" className="inline-flex items-center rounded-md border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground text-xs bg-slate-400/10 border-slate-400"
data-sentry-component="Badge" data-sentry-component="Badge"
data-sentry-source-file="badge.tsx" data-sentry-source-file="badge.tsx"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-code-xml h-3 w-3 mr-1"
> >
<path d="m18 16 4-4-4-4" /> <svg
<path d="m6 8-4 4 4 4" /> xmlns="http://www.w3.org/2000/svg"
<path d="m14.5 4-5 16" /> width="24"
</svg> height="24"
{lang} viewBox="0 0 24 24"
</div> fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-code-xml h-3 w-3 mr-1"
>
<path d="m18 16 4-4-4-4" />
<path d="m6 8-4 4 4 4" />
<path d="m14.5 4-5 16" />
</svg>
{lang}
</div>
)} )}
</div> </div>
</div> </div>
@@ -142,15 +142,19 @@ export default function PinnedRepos() {
const [pinned, setPinned] = useState<PinnedRepo[]>([]); const [pinned, setPinned] = useState<PinnedRepo[]>([]);
useEffect(() => { useEffect(() => {
fetch("https://pinned.berrysauce.dev/get/kleo-dev").then(r => { fetch("https://pinned.berrysauce.dev/get/kleo-dev")
r.json().then(p => { .then((r) => {
setPinned(p); r.json()
}).catch(e => { .then((p) => {
console.error(e) setPinned(p);
})
.catch((e) => {
console.error(e);
});
}) })
}).catch(e => { .catch((e) => {
console.error(e) console.error(e);
}) });
}); });
return ( return (
@@ -170,8 +174,6 @@ export default function PinnedRepos() {
</div> </div>
); );
} catch (e: any) { } catch (e: any) {
return ( return <p>{e.toString()}</p>;
<p>{e.toString()}</p>
);
} }
} }
+2 -3
View File
@@ -17,13 +17,12 @@ export default function Skill({
bg-black text-white text-sm px-2 py-1 rounded bg-black text-white text-sm px-2 py-1 rounded
opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap" opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap"
> >
Skilled in {" "} {skill} Skilled in {skill}
</span> </span>
<FontAwesomeIcon <FontAwesomeIcon
icon={icon} icon={icon}
width={40} className={`text-gray-300 ${classInsert} size-[2.3rem]`}
className={`text-red ${classInsert} cursor-pointer`}
/> />
</div> </div>
); );