From 5b34e54aabe56c5da71f43c2dfc234622c5e30d6 Mon Sep 17 00:00:00 2001 From: Lu <42706009+luinbytes@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:25:11 +0000 Subject: [PATCH 1/3] Add quick stats and language breakdown to Activity Introduces state and logic to fetch and display quick stats such as repository count, top languages, current contribution streak, and yearly commits in the Activity component. Adds utility functions for language breakdown and streak calculation, and fetches relevant data from the GitHub API. --- components/sections/activity.tsx | 104 +++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/components/sections/activity.tsx b/components/sections/activity.tsx index 7f365b5..803648f 100644 --- a/components/sections/activity.tsx +++ b/components/sections/activity.tsx @@ -9,6 +9,79 @@ export function Activity() { const [commits, setCommits] = useState([]); const [calendarData, setCalendarData] = useState([]); + // Quick Stats state + const [repoCount, setRepoCount] = useState(0); + const [languages, setLanguages] = useState<{ name: string; percentage: number; color: string }[]>([]); + const [currentStreak, setCurrentStreak] = useState(0); + const [yearlyCommits, setYearlyCommits] = useState(0); + const [statsLoaded, setStatsLoaded] = useState(false); + + // Language colors mapping + const languageColors: Record = { + TypeScript: '#3178c6', + JavaScript: '#f1e05a', + Python: '#3572A5', + Rust: '#dea584', + 'C#': '#178600', + Java: '#b07219', + Go: '#00ADD8', + Ruby: '#701516', + PHP: '#4F5D95', + CSS: '#563d7c', + HTML: '#e34c26', + }; + + // Calculate language breakdown from repos + const calculateLanguages = (repos: any[]) => { + const langCount: Record = {}; + repos.forEach(repo => { + if (repo.language) { + langCount[repo.language] = (langCount[repo.language] || 0) + 1; + } + }); + + const total = Object.values(langCount).reduce((a, b) => a + b, 0); + if (total === 0) return []; + + return Object.entries(langCount) + .map(([lang, count]) => ({ + name: lang, + percentage: Math.round((count / total) * 100), + color: languageColors[lang] || '#6e7681' + })) + .sort((a, b) => b.percentage - a.percentage) + .slice(0, 5); + }; + + // Calculate current streak from contribution data + const calculateStreak = (contributions: any[]) => { + if (!contributions || contributions.length === 0) return 0; + + let streak = 0; + const today = new Date(); + today.setHours(0, 0, 0, 0); + + // Sort by date descending + const sorted = [...contributions].sort((a, b) => + new Date(b.date).getTime() - new Date(a.date).getTime() + ); + + for (const day of sorted) { + const dayDate = new Date(day.date); + dayDate.setHours(0, 0, 0, 0); + + const daysDiff = Math.floor((today.getTime() - dayDate.getTime()) / (1000 * 60 * 60 * 24)); + + if (daysDiff === streak && day.count > 0) { + streak++; + } else if (daysDiff > streak) { + break; + } + } + + return streak; + }; + useEffect(() => { // Fetch recent events fetch("https://api.github.com/users/luinbytes/events/public") @@ -47,10 +120,41 @@ export function Activity() { level: day.level })); setCalendarData(formatted); + + // Calculate streak and yearly commits from contribution data + const streak = calculateStreak(formatted); + setCurrentStreak(streak); + + const currentYear = new Date().getFullYear(); + const thisYearContributions = formatted + .filter((day: any) => new Date(day.date).getFullYear() === currentYear) + .reduce((sum: number, day: any) => sum + day.count, 0); + setYearlyCommits(thisYearContributions); } }) .catch(err => console.error("Failed to fetch calendar", err)); + // Fetch GitHub repos for language stats + fetch("https://api.github.com/users/luinbytes/repos?per_page=100") + .then(res => res.json()) + .then(repos => { + if (Array.isArray(repos)) { + // Filter out forks + const ownRepos = repos.filter((repo: any) => !repo.fork); + setRepoCount(ownRepos.length); + + // Calculate language breakdown + const langs = calculateLanguages(ownRepos); + setLanguages(langs); + + setStatsLoaded(true); + } + }) + .catch(err => { + console.error("Failed to fetch repos", err); + setStatsLoaded(true); + }); + }, []); return ( From e9cce98c2e1814df5175ab3c7a550a742cb39cac Mon Sep 17 00:00:00 2001 From: Lu <42706009+luinbytes@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:26:12 +0000 Subject: [PATCH 2/3] Replace Build Log with Quick Dev Stats in Activity section The Build Log section in the Activity component has been replaced with a new Quick Dev Stats panel, displaying live GitHub statistics such as public repos, yearly commits, top languages, and current streak. Related data structures and exports for the Build Log have been removed from lib/data.ts to reflect this change. --- components/sections/activity.tsx | 104 +++++++++++++++++++++---------- lib/data.ts | 12 +--- 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/components/sections/activity.tsx b/components/sections/activity.tsx index 803648f..9858c14 100644 --- a/components/sections/activity.tsx +++ b/components/sections/activity.tsx @@ -223,48 +223,84 @@ export function Activity() { - {/* Right Column: Build Log */} + {/* Right Column: Quick Dev Stats */}

- Build Log / now + Quick Stats / live

-
-
-
-
-
- ~/current-tasks + {!statsLoaded ? ( +
+
Loading GitHub stats...
-
-
- ➜ - Updating Raycast extensions -
-
- ➜ - Refactoring portfolio sites -
-
- ➜ - Playing around with Python scripts + ) : ( + <> + {/* Stats Cards Grid */} +
+
+
{repoCount}
+
Public Repos
+
+
+
{yearlyCommits}
+
This Year
+
-
- ➜ - + + {/* Language Breakdown */} +
+

Top Languages

+
+ {languages.map((lang, index) => ( +
+
+
+
+ {lang.name} +
+ {lang.percentage}% +
+
+
+
+
+ ))} +
-
-
-
-

Check the real source.

-

- My activity above is pulled directly from the GitHub API. -

- - Visit GitHub Profile → - -
+ {/* Streak Display */} + {currentStreak > 0 && ( +
+
+ πŸ”₯ +
+
{currentStreak} day streak
+
Keep the momentum going!
+
+
+
+ )} + +
+

Real-time data.

+

+ These stats are pulled live from the GitHub API. +

+ + Visit GitHub Profile → + +
+ + )}
diff --git a/lib/data.ts b/lib/data.ts index 1b66b64..0c494b1 100644 --- a/lib/data.ts +++ b/lib/data.ts @@ -23,10 +23,7 @@ export interface ActivityItem { date: string; // ISO string or loose date "Dec 2025" } -export interface BuildLogItem { - id: string; - content: string; -} +// BuildLogItem interface removed - Build Log section replaced with Quick Dev Stats export const projects: Project[] = [ { @@ -130,9 +127,4 @@ export const recentActivity: ActivityItem[] = [ } ]; -export const buildLog: BuildLogItem[] = [ - { id: "1", content: "Experimenting with a new Raycast extension that manages PC gaming profiles" }, - { id: "2", content: "Building an AI-powered helper for managing Steam libraries" }, - { id: "3", content: "Refactoring my home server backup scripts to use Restic" }, - { id: "4", content: "Trying to make my mechanical keyboard firmware do my taxes (kidding... unless?)" }, -]; +// buildLog export removed - Build Log section replaced with Quick Dev Stats in activity.tsx From eec418e2cf50a9d1b24ea7efb2804cc8daa5dbe7 Mon Sep 17 00:00:00 2001 From: Lu <42706009+luinbytes@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:35:19 +0000 Subject: [PATCH 3/3] Refine section layouts and improve 2FA code logic Adjusted padding, spacing, and container widths across About, Activity, Projects, Contact, and Automation Playground sections for improved visual consistency. Updated the Projects section order in the homepage. Enhanced the 2FA code logic in ASFBotDemo to generate and update codes on interval, ensuring more realistic behavior. --- app/page.tsx | 5 +++-- components/sections/about.tsx | 12 +++++------ components/sections/activity.tsx | 8 +++---- components/sections/automation-playground.tsx | 21 +++++++++++++++---- components/sections/contact.tsx | 10 ++++----- components/sections/hero.tsx | 4 ++-- components/sections/projects.tsx | 18 ++++++++-------- 7 files changed, 46 insertions(+), 32 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 0ab5648..2f3f9b2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -9,10 +9,11 @@ export default function Home() { return (
- +
- + +
); diff --git a/components/sections/about.tsx b/components/sections/about.tsx index e58d1bf..4fd5814 100644 --- a/components/sections/about.tsx +++ b/components/sections/about.tsx @@ -15,16 +15,16 @@ export function About() { ]; return ( -
-
-
+
+
+
-

+

/ About Me

-
+

I’m a self-taught software engineer who learns by breaking things. My journey didn't start with "Hello World"β€”it started with the PlayStation 3 and Call of Duty: World at War.

@@ -42,7 +42,7 @@ export function About() { Milestones -
+
{milestones.map((m, i) => (
diff --git a/components/sections/activity.tsx b/components/sections/activity.tsx index 9858c14..cc0b8a4 100644 --- a/components/sections/activity.tsx +++ b/components/sections/activity.tsx @@ -158,12 +158,12 @@ export function Activity() { }, []); return ( -
-
+
+
{/* Left Column: Contributions & Graph */}
-

+

Activity

@@ -225,7 +225,7 @@ export function Activity() { {/* Right Column: Quick Dev Stats */}
-

+

Quick Stats / live

diff --git a/components/sections/automation-playground.tsx b/components/sections/automation-playground.tsx index e5caeaa..3cf42e0 100644 --- a/components/sections/automation-playground.tsx +++ b/components/sections/automation-playground.tsx @@ -9,7 +9,7 @@ export function AutomationPlayground() {
-
+

@@ -21,7 +21,7 @@ export function AutomationPlayground() {

-
+
@@ -161,6 +161,7 @@ function DiscordUtilitiesDemo() { function ASFBotDemo() { const [selectedBot, setSelectedBot] = useState(0); const [twoFATime, setTwoFATime] = useState(30); + const [twoFACode, setTwoFACode] = useState('123 456'); const bots = [ { id: 'bot1', name: 'MainAccount', status: 'Farming', game: 'Team Fortress 2', cards: '3/5', online: true }, @@ -168,10 +169,22 @@ function ASFBotDemo() { { id: 'bot3', name: 'TradingBot', status: 'Offline', game: null, cards: '0/0', online: false }, ]; + const generateCode = () => { + const code1 = Math.floor(Math.random() * 900 + 100); + const code2 = Math.floor(Math.random() * 900 + 100); + return `${code1} ${code2}`; + }; + useEffect(() => { + // Generate initial code on client + setTwoFACode(generateCode()); + const interval = setInterval(() => { setTwoFATime(prev => { - if (prev <= 1) return 30; + if (prev <= 1) { + setTwoFACode(generateCode()); + return 30; + } return prev - 1; }); }, 1000); @@ -242,7 +255,7 @@ function ASFBotDemo() {
- {Math.floor(Math.random() * 900000 + 100000).toString().slice(0, 3)} {Math.floor(Math.random() * 900000 + 100000).toString().slice(0, 3)} + {twoFACode}