diff --git a/public/logo.png b/public/logo.png index 9014853..af0314a 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/src/app/api/contribs/route.ts b/src/app/api/contribs/route.ts new file mode 100644 index 0000000..a107556 --- /dev/null +++ b/src/app/api/contribs/route.ts @@ -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 }); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e52aaa9..7db9358 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,9 +17,7 @@ export default function RootLayout({ -
- {children} -
+
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx index 97d8980..41c8f45 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,5 @@ +"use client"; + import PinnedRepos from "@/components/PinnedRepos"; import Skill from "@/components/Skill"; import { @@ -12,20 +14,33 @@ import { } from "@fortawesome/free-brands-svg-icons"; import { faJava } from "@fortawesome/free-brands-svg-icons/faJava"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useState } from "react"; import GitHubCalendar from "react-github-calendar"; export default function App() { + const [contribs, setContribs] = useState(); + + 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 (
-
+

Hey, I'm Klesti! 👋🏻

A 15 year old Software Engineer from Tirane, Albania, with 6 years of experience, specializing in full-stack web development, desktop - applications, TUI apps, and hacking. I also have a deep interest in - low-level programming. + applications, TUI apps, and hacking. I also have a deep interest + in low-level programming.

{/* Socials and skill */} @@ -35,8 +50,7 @@ export default function App() { - +
@@ -70,17 +85,42 @@ export default function App() {
- -

Pinned Projects

+ +

Pinned Projects

-
- +

+ {contribs || "Loading.."} contributions in the last year +

+
+
+
+ If you see this i ❤️ you +

+ © 2025 Klesti. All rights reserved. +

+
diff --git a/src/components/Background.tsx b/src/components/Background.tsx index 56234b4..c0e6291 100644 --- a/src/components/Background.tsx +++ b/src/components/Background.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; import React, { useEffect, useRef, useState } from "react"; @@ -7,183 +7,74 @@ export default function Background({ }: Readonly<{ children: React.ReactNode; }>) { - const canvasRef = useRef(null); - const animationRef = useRef(0); - const particlesRef = useRef([]); - 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 }); - }; + const [pos, setPos] = useState({ x: 30, y: 100 }); + const [vel, setVel] = useState({ x: 1, y: 1 }); useEffect(() => { - handleResize(); - window.addEventListener("resize", handleResize); + let frame: number; + let x = pos.x; + let y = pos.y; + let vx = vel.x; + let vy = vel.y; - return () => { - window.removeEventListener("resize", handleResize); - if (animationRef.current) { - cancelAnimationFrame(animationRef.current); - } + const speed = 0.5; + const size = 100; + + // 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 (
- - - {children} + Logo +
{children}
); -}; +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index d2e6226..10aa453 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -13,8 +13,8 @@ export default function Navbar() { Home @@ -22,8 +22,8 @@ export default function Navbar() { Pricing diff --git a/src/components/PinnedRepos.tsx b/src/components/PinnedRepos.tsx index 229cea7..cf2c861 100644 --- a/src/components/PinnedRepos.tsx +++ b/src/components/PinnedRepos.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; import { useEffect, useState } from "react"; @@ -31,7 +31,7 @@ export function Repo({ rounded-xl h-full border - border-red + border-slate-300 hover:shadow-md transition-shadow duration-300 @@ -41,7 +41,7 @@ export function Repo({
)}
@@ -142,15 +142,19 @@ export default function PinnedRepos() { const [pinned, setPinned] = useState([]); useEffect(() => { - fetch("https://pinned.berrysauce.dev/get/kleo-dev").then(r => { - r.json().then(p => { - setPinned(p); - }).catch(e => { - console.error(e) + fetch("https://pinned.berrysauce.dev/get/kleo-dev") + .then((r) => { + r.json() + .then((p) => { + setPinned(p); + }) + .catch((e) => { + console.error(e); + }); }) - }).catch(e => { - console.error(e) - }) + .catch((e) => { + console.error(e); + }); }); return ( @@ -170,8 +174,6 @@ export default function PinnedRepos() { ); } catch (e: any) { - return ( -

{e.toString()}

- ); + return

{e.toString()}

; } } diff --git a/src/components/Skill.tsx b/src/components/Skill.tsx index b9158b1..de9fc89 100644 --- a/src/components/Skill.tsx +++ b/src/components/Skill.tsx @@ -17,13 +17,12 @@ export default function Skill({ bg-black text-white text-sm px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap" > - Skilled in {" "} {skill} + Skilled in {skill} );