+
PRO
)}
-
- {profile.name}
-
+ {profile.name}
+
+ {/* Animated Developer Persona Badge */}
+
+ {/* Subtle background glow that pulses */}
+
+
+
+ {persona.name}
+
+
+
@{profile.username}
{profile.bio}
@@ -791,9 +896,41 @@ export default function CompareClient() {
const [user1, setUser1] = useState(searchParams.get('user1') || '');
const [user2, setUser2] = useState(searchParams.get('user2') || '');
- const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
+ const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
+ const [isExporting, setIsExporting] = useState(false);
+
+ const captureRef = useRef(null);
+
+ const handleDownloadCard = async () => {
+ if (!captureRef.current || !data) return;
+ setIsExporting(true);
+
+ try {
+ // Small delay to allow export overlay/scanning UI to render first
+ await new Promise((res) => setTimeout(res, 300));
+
+ const canvas = await html2canvas(captureRef.current, {
+ scale: 2, // higher resolution
+ backgroundColor: document.documentElement.classList.contains('dark')
+ ? '#000000'
+ : '#ffffff',
+ useCORS: true,
+ logging: false,
+ });
+
+ const image = canvas.toDataURL('image/png');
+ const link = document.createElement('a');
+ link.href = image;
+ link.download = `commitpulse-battle-${data.user1.profile.username}-vs-${data.user2.profile.username}.png`;
+ link.click();
+ } catch (err) {
+ console.error('Failed to export image', err);
+ } finally {
+ setIsExporting(false);
+ }
+ };
const BASE_URL =
typeof window !== 'undefined' ? window.location.origin : 'https://commitpulse.vercel.app';
@@ -871,275 +1008,338 @@ export default function CompareClient() {
}
return (
-
-
- {/* Page Header */}
-
-
-
-
- Developer Showdown
-
-
-
- Compare Developers
-
-
- Put two GitHub profiles head-to-head. Streaks, contributions, languages — who comes out
- on top?
-
-
-
- {/* Input Form */}
-
-
-
-
-
setUser1(e.target.value)}
- onKeyDown={(e) => e.key === 'Enter' && handleCompare(user1, user2)}
- className="w-full pl-10 pr-4 py-3 rounded-xl border border-black/10 dark:border-[rgba(255,255,255,0.1)] bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white text-sm placeholder:text-[#A1A1AA] focus:outline-none focus:border-emerald-500/50 transition-colors"
- />
+ <>
+
+
+
+
+
+ {/* Page Header */}
+
+
+
+
+ Developer Showdown
+
+
+ Compare Developers
+
+
+ Put two GitHub profiles head-to-head. Streaks, contributions, languages — who comes
+ out on top?
+
+
-
- VS
-
+ {/* Input Form */}
+
+
+
+
+ setUser1(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && handleCompare(user1, user2)}
+ className="w-full pl-10 pr-4 py-3 rounded-xl border border-black/10 dark:border-[rgba(255,255,255,0.1)] bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white text-sm placeholder:text-[#A1A1AA] focus:outline-none focus:border-emerald-500/50 transition-colors"
+ />
+
+
+
+ VS
+
-
+
-
handleCompare(user1, user2)}
- disabled={loading}
- className="flex items-center justify-center gap-2 px-6 py-3 rounded-xl bg-black dark:bg-white text-white dark:text-black text-sm font-semibold hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- >
- {loading ? : }
- {loading ? 'Comparing...' : 'Compare'}
-
-
-
+ {/* Error */}
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+ {/* Loading */}
+ {loading &&
}
+
+ {/* Results */}
+
+ {d1 && d2 && !loading && (
+
+ {/* Optional: Spotify Wrapped style header only visible in the canvas or just part of the card */}
+
+
+
- {/* Error */}
-
- {error && (
-
- {error}
-
- )}
-
+
+ {/* Winner Banner */}
+ {winner && (
+
+
+
+
+ {winner === 'tie'
+ ? "It's a Tie! Both developers are evenly matched."
+ : `@${winner} wins the showdown!`}
+
+
+
+ )}
- {/* Loading */}
- {loading &&
}
+ {/* Profile Cards */}
+
+
- {/* Results */}
-
- {d1 && d2 && !loading && (
-
- {/* Winner Banner */}
- {winner && (
-
-
-
-
- {winner === 'tie'
- ? "It's a Tie! Both developers are evenly matched."
- : `@${winner} wins the showdown!`}
-
-
-
- )}
+ {/* VS Divider */}
+
- {/* Profile Cards */}
-
-
+
+
- {/* VS Divider */}
-
-
-
VS
+ {/* Stats Battle Grid */}
+
+
+ Stats Showdown
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+ {/* Coding Habits Showdown */}
+
- {/* Stats Battle Grid */}
-
-
- Stats Showdown
-
-
-
-
-
-
-
-
-
-
+
+ {/* Developer Skills Radar */}
+
+
+ {/* Language Comparison */}
+
+
+ {/* Activity Heatmaps */}
+
+ {[
+ { user: d1, side: 'left' as const },
+ { user: d2, side: 'right' as const },
+ ].map(({ user, side }) => (
+
+
+ {user.profile.username}'s Activity (Last 13 Weeks)
+
+
+
+ ))}
+
+
+ {/* 3D Monolith Embeds */}
+
+
+ 3D Monolith Comparison
+
+
+ {[d1, d2].map((user) => (
+
+
+
+ @{user.profile.username}
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+
+ ))}
+
+
-
- {/* Coding Habits Showdown */}
-
-
- {/* Code Volume Showdown */}
-
-
- {/* Developer Skills Radar */}
-
-
- {/* Language Comparison */}
-
-
- {/* Activity Heatmaps */}
-
- {[
- { user: d1, side: 'left' as const },
- { user: d2, side: 'right' as const },
- ].map(({ user, side }) => (
-
+
-
- {user.profile.username}'s Activity (Last 13 Weeks)
-
-
-
- ))}
-
+
+ {isExporting ? : }
+
+ {isExporting ? 'Generating Epic Card...' : 'Export Wrapped Card'}
+
+ {/* Subtle glare effect on hover */}
+
+
+
- {/* 3D Monolith Embeds */}
-
-
- 3D Monolith Comparison
-
-
- {[d1, d2].map((user) => (
+ {/* Fullscreen Scanner Overlay during export */}
+
+ {isExporting && (
-
-
- @{user.profile.username}
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
+
+ Capturing the Showdown...
+
- ))}
-
-
-
- )}
-
-
-
+ )}
+
+
+ )}
+
+
+
+ >
);
}
diff --git a/app/layout.tsx b/app/layout.tsx
index 7250073c..b72fe23b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -7,6 +7,7 @@ import ReturnToTop from '@/components/ReturnToTop';
import type { Metadata } from 'next';
import ScrollRestoration from './components/ScrollRestoration';
import AnimatedCursor from '@/components/AnimatedCursor';
+import KonamiEasterEgg from '@/components/KonamiEasterEgg';
const inter = Inter({ subsets: ['latin'] });
@@ -93,6 +94,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{children}
+