Animated look (way better)

This commit is contained in:
2025-12-09 11:51:05 +01:00
parent 4fc62d6045
commit 41214ffc94
5 changed files with 252 additions and 83 deletions
+43
View File
@@ -17,6 +17,7 @@
"@notionhq/client": "^2.2.15", "@notionhq/client": "^2.2.15",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"framer-motion": "^12.23.25",
"katex": "^0.16.19", "katex": "^0.16.19",
"lucide-react": "^0.554.0", "lucide-react": "^0.554.0",
"next": "^16.0.7", "next": "^16.0.7",
@@ -1600,6 +1601,33 @@
"node": ">=0.4.x" "node": ">=0.4.x"
} }
}, },
"node_modules/framer-motion": {
"version": "12.23.25",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.25.tgz",
"integrity": "sha512-gUHGl2e4VG66jOcH0JHhuJQr6ZNwrET9g31ZG0xdXzT0CznP7fHX4P8Bcvuc4MiUB90ysNnWX2ukHRIggkl6hQ==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3273,6 +3301,21 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+1
View File
@@ -18,6 +18,7 @@
"@notionhq/client": "^2.2.15", "@notionhq/client": "^2.2.15",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"framer-motion": "^12.23.25",
"katex": "^0.16.19", "katex": "^0.16.19",
"lucide-react": "^0.554.0", "lucide-react": "^0.554.0",
"next": "^16.0.7", "next": "^16.0.7",
+151 -56
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import { motion, spring } from "framer-motion";
import PinnedRepos from "@/components/PinnedRepos"; import PinnedRepos from "@/components/PinnedRepos";
import Skill from "@/components/Skill"; import Skill from "@/components/Skill";
import { import {
@@ -17,6 +18,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CoffeeIcon, HeartIcon } from "lucide-react"; import { CoffeeIcon, HeartIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import GitHubCalendar from "react-github-calendar"; import GitHubCalendar from "react-github-calendar";
import { fadeInUp, popIn, staggerContainer } from "@/components/effects";
export default function App() { export default function App() {
const [contribs, setContribs] = useState<number>(); const [contribs, setContribs] = useState<number>();
@@ -25,81 +27,154 @@ export default function App() {
const fetchContributions = async () => { const fetchContributions = async () => {
const res = await fetch("/api/contribs"); const res = await fetch("/api/contribs");
const data = await res.json(); const data = await res.json();
console.log("Total contributions:", data.total);
setContribs(data.total || undefined); setContribs(data.total || undefined);
}; };
fetchContributions(); fetchContributions();
}, []); }, []);
return ( return (
<div className="w-full max-w-screen-lg mx-auto animate-fadeIn "> <motion.div
className="w-full max-w-screen-lg mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6 }}
>
<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> {/* HEADER */}
<p className="font-semibold text-gray-300"> <motion.h1
className="py-2 text-gray-300"
variants={fadeInUp}
initial="hidden"
animate="show"
>
Hey, I'm Klesti! 👋🏻
</motion.h1>
<motion.p
className="font-semibold text-gray-300"
variants={fadeInUp}
initial="hidden"
animate="show"
transition={{ delay: 0.15 }}
>
A 15 year old Software Engineer from Tirane, Albania, with 6 years of A 15 year old Software Engineer from Tirane, Albania, with 6 years of
experience, specializing in full-stack web development, desktop 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 in
low-level programming. low-level programming.
</p> </motion.p>
<div className="pt-6"> {/* KO-FI BUTTON */}
<motion.div
className="pt-6"
animate={{ y: [0, -3, 0] }}
transition={{ repeat: Infinity, duration: 3 }}
>
<a href="https://ko-fi.com/X8X01OX1WR" target="_blank"> <a href="https://ko-fi.com/X8X01OX1WR" target="_blank">
<span className="bg-transparent-ish rounded-md border border-slate-400 p-2 flex w-max gap-2 items-center"> <motion.span
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="bg-transparent-ish rounded-md border border-slate-400 p-2 flex w-max gap-2 items-center cursor-pointer"
>
<CoffeeIcon /> Support me on Ko-fi <CoffeeIcon /> Support me on Ko-fi
</span> </motion.span>
</a> </a>
</div> </motion.div>
{/* Socials and skill */} {/* SOCIALS + SKILLS */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6 bg-transparent"> <motion.div
className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6 bg-transparent"
variants={staggerContainer}
initial="hidden"
animate="show"
>
{/* socials */} {/* socials */}
<div className="pt-5 flex flex-row gap-5 w-max"> <motion.div className="pt-5 flex flex-row gap-5 w-max">
<a href="https://github.com/selimaj-dev/" target="blank_"> {[faGithub, faLinkedin, faInstagram].map((icon, i) => (
<FontAwesomeIcon <motion.a
icon={faGithub} key={i}
className="text-slate-400 size-[2.3rem]" href={
/> icon === faGithub
</a> ? "https://github.com/selimaj-dev/"
<a : icon === faLinkedin
href="https://www.linkedin.com/in/klesti-selimaj-7a0162343/" ? "https://www.linkedin.com/in/klesti-selimaj-7a0162343/"
: "https://www.instagram.com/selimaj.dev/"
}
target="blank_" target="blank_"
variants={popIn}
whileHover={{ scale: 1.2, rotate: 3 }}
whileTap={{ scale: 0.9 }}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faLinkedin} icon={icon}
className="text-slate-400 size-[2.3rem]" className="text-slate-400 size-[2.3rem]"
/> />
</a> </motion.a>
<a href="https://www.instagram.com/selimaj.dev/" target="blank_"> ))}
<FontAwesomeIcon </motion.div>
icon={faInstagram}
className="text-slate-400 size-[2.3rem]"
/>
</a>
</div>
<div>
<div className="p-3 rounded-xl">
<div className="mx-auto w-max flex flex-row gap-5">
<Skill skill="Rust" icon={faRust} />
<Skill skill="Go" icon={faGolang} />
<Skill skill="JavaScript" icon={faJs} />
<Skill skill="Java" icon={faJava} classInsert="p-1" />
<Skill skill="Python" icon={faPython} />
<Skill skill="React" icon={faReact} />
</div>
</div>
</div>
</div>
<h1 className="text-slate-300">Pinned Projects</h1> {/* skills */}
<section className=""> <motion.div variants={popIn}>
<div className="p-3 rounded-xl">
<motion.div
className="mx-auto w-max flex flex-row gap-5"
variants={staggerContainer}
initial="hidden"
animate="show"
>
<motion.div variants={popIn}>
<Skill skill="Rust" icon={faRust} />
</motion.div>
<motion.div variants={popIn}>
<Skill skill="Go" icon={faGolang} />
</motion.div>
<motion.div variants={popIn}>
<Skill skill="JavaScript" icon={faJs} />
</motion.div>
<motion.div variants={popIn}>
<Skill skill="Java" icon={faJava} classInsert="p-1" />
</motion.div>
<motion.div variants={popIn}>
<Skill skill="Python" icon={faPython} />
</motion.div>
<motion.div variants={popIn}>
<Skill skill="React" icon={faReact} />
</motion.div>
</motion.div>
</div>
</motion.div>
</motion.div>
{/* PINNED PROJECTS */}
<motion.h1
className="text-slate-300 mt-4"
variants={fadeInUp}
initial="hidden"
animate="show"
>
Pinned Projects
</motion.h1>
<motion.section>
<PinnedRepos /> <PinnedRepos />
</section> </motion.section>
<h2 className="text-slate-300 mt-7">
{/* CONTRIBUTIONS */}
<motion.h2
className="text-slate-300 mt-7"
variants={fadeInUp}
initial="hidden"
animate="show"
>
{contribs || "Loading.."} contributions in the last year {contribs || "Loading.."} contributions in the last year
</h2> </motion.h2>
<section className="mt-4">
<motion.section
className="mt-4"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
>
<GitHubCalendar <GitHubCalendar
hideTotalCount // hideTotalCount
username="selimaj-dev" username="selimaj-dev"
theme={{ theme={{
light: [ light: [
@@ -118,16 +193,36 @@ export default function App() {
], ],
}} }}
/> />
</section> </motion.section>
{/* FOOTER */}
<footer className="mt-8 border-t pt-3 pb-8"> <footer className="mt-8 border-t pt-3 pb-8">
<p className="flex gap-1 items-center"> <motion.p
If you see this i <HeartIcon className="size-4" /> you className="flex gap-1 items-center"
</p> initial={{ opacity: 0 }}
<p className="text-slate-300 mx-auto w-max mt-2"> animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
>
If you see this i{" "}
<motion.span
animate={{ scale: [1, 1.2, 1] }}
transition={{ repeat: Infinity, duration: 1.3 }}
>
<HeartIcon className="size-4" />
</motion.span>{" "}
you
</motion.p>
<motion.p
className="text-slate-300 mx-auto w-max mt-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6 }}
>
© 2025 Klesti. All rights reserved. © 2025 Klesti. All rights reserved.
</p> </motion.p>
</footer> </footer>
</div> </div>
</div> </motion.div>
); );
} }
+17 -19
View File
@@ -1,6 +1,8 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { motion } from "framer-motion";
import { fadeInUp, fadeInUp2, staggerContainer } from "./effects";
type PinnedRepo = { type PinnedRepo = {
author: string; author: string;
@@ -21,22 +23,12 @@ export function Repo({
href, href,
}: PinnedRepo) { }: PinnedRepo) {
return ( return (
<div <motion.div
className={` variants={fadeInUp2}
group initial="hidden"
relative whileInView="visible"
flex viewport={{ once: true, amount: 0.2 }}
size-full className="group relative flex size-full overflow-hidden rounded-xl h-full border border-slate-400 hover:shadow-md transition-shadow duration-300 backdrop-blur-[2px]"
overflow-hidden
rounded-xl
h-full
border
border-slate-400
hover:shadow-md
transition-shadow
duration-300
backdrop-blur-[2px]
`}
> >
<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">
@@ -133,7 +125,7 @@ export function Repo({
</div> </div>
</div> </div>
</div> </div>
</div> </motion.div>
); );
} }
@@ -158,7 +150,13 @@ export default function PinnedRepos() {
}); });
return ( return (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6 bg-transparent"> <motion.div
variants={staggerContainer}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.2 }}
className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6 bg-transparent"
>
{pinned && {pinned &&
pinned.map((repo, i) => ( pinned.map((repo, i) => (
<Repo <Repo
@@ -172,7 +170,7 @@ export default function PinnedRepos() {
href={`https://github.com/${repo.author}/${repo.name}`} href={`https://github.com/${repo.author}/${repo.name}`}
/> />
))} ))}
</div> </motion.div>
); );
} catch (e: any) { } catch (e: any) {
return <p>{e.toString()}</p>; return <p>{e.toString()}</p>;
+32
View File
@@ -0,0 +1,32 @@
import { easeOut, spring } from "framer-motion";
export const fadeInUp = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { duration: 0.5 } },
};
export const fadeInUp2 = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: easeOut },
},
};
export const staggerContainer = {
show: {
transition: {
staggerChildren: 0.12,
},
},
};
export const popIn = {
hidden: { opacity: 0, scale: 0.8 },
show: {
opacity: 1,
scale: 1,
transition: { type: spring, stiffness: 120 },
},
};