@@ -19,6 +19,11 @@ import {
1919 Moon ,
2020 Sun ,
2121 Coffee ,
22+ Plus ,
23+ Minus ,
24+ Code2 ,
25+ GitPullRequest ,
26+ CircleDot ,
2227} from 'lucide-react' ;
2328
2429/* ── types ────────────────────────────────────────────────────────────── */
@@ -45,6 +50,8 @@ interface UserStats {
4550 peakStreak : number ;
4651 totalContributions : number ;
4752 codingHabit ?: string ;
53+ totalPRs ?: number ;
54+ totalIssues ?: number ;
4855}
4956
5057interface LanguageData {
@@ -57,6 +64,8 @@ interface ActivityData {
5764 date : string ;
5865 count : number ;
5966 intensity : 0 | 1 | 2 | 3 | 4 ;
67+ locAdditions ?: number ;
68+ locDeletions ?: number ;
6069}
6170
6271interface CompareUserData {
@@ -498,6 +507,123 @@ function CodingHabitShowdown({ user1, user2 }: { user1: CompareUserData; user2:
498507 ) ;
499508}
500509
510+ /* ── helper: code volume showdown ─────────────────────────────────────── */
511+
512+ function CodeVolumeShowdown ( { user1, user2 } : { user1 : CompareUserData ; user2 : CompareUserData } ) {
513+ const calcLoC = ( activity : ActivityData [ ] ) => {
514+ let add = 0 ,
515+ del = 0 ;
516+ activity . forEach ( ( d ) => {
517+ add += d . locAdditions || 0 ;
518+ del += d . locDeletions || 0 ;
519+ } ) ;
520+ return { add, del, net : add - del } ;
521+ } ;
522+
523+ const loc1 = calcLoC ( user1 . activity ) ;
524+ const loc2 = calcLoC ( user2 . activity ) ;
525+
526+ const maxAdd = Math . max ( loc1 . add , loc2 . add , 1 ) ;
527+ const maxDel = Math . max ( loc1 . del , loc2 . del , 1 ) ;
528+
529+ const users = [
530+ { username : user1 . profile . username , loc : loc1 , side : 'left' as const } ,
531+ { username : user2 . profile . username , loc : loc2 , side : 'right' as const } ,
532+ ] ;
533+
534+ return (
535+ < div >
536+ < h2 className = "text-xs text-[#A1A1AA] uppercase tracking-widest font-medium mb-4 flex items-center gap-2" >
537+ < Code2 size = { 14 } className = "text-violet-400" />
538+ Code Volume
539+ </ h2 >
540+ < div className = "grid grid-cols-1 md:grid-cols-2 gap-6" >
541+ { users . map ( ( { username, loc, side } ) => (
542+ < motion . div
543+ key = { side }
544+ initial = { { opacity : 0 , x : side === 'left' ? - 20 : 20 } }
545+ whileInView = { { opacity : 1 , x : 0 } }
546+ viewport = { { once : true } }
547+ whileHover = { { scale : 1.01 } }
548+ className = "relative overflow-hidden p-6 rounded-2xl bg-white dark:bg-[#0a0a0a] border border-black/10 dark:border-[rgba(255,255,255,0.08)] transition-all duration-300"
549+ >
550+ < h4 className = "text-xs font-bold uppercase tracking-widest text-[#A1A1AA] mb-5" >
551+ @{ username }
552+ </ h4 >
553+
554+ { /* Additions Bar */ }
555+ < div className = "mb-4" >
556+ < div className = "flex justify-between items-center mb-2" >
557+ < span className = "flex items-center gap-1.5 text-xs font-medium text-emerald-500" >
558+ < Plus size = { 12 } /> Lines Added
559+ </ span >
560+ < motion . span
561+ initial = { { opacity : 0 } }
562+ whileInView = { { opacity : 1 } }
563+ className = "text-sm font-bold text-emerald-500"
564+ >
565+ +{ loc . add . toLocaleString ( ) }
566+ </ motion . span >
567+ </ div >
568+ < div className = "w-full h-3 bg-gray-100 dark:bg-[#111] rounded-full overflow-hidden border border-black/5 dark:border-[rgba(255,255,255,0.04)]" >
569+ < motion . div
570+ initial = { { width : 0 } }
571+ whileInView = { { width : `${ ( loc . add / maxAdd ) * 100 } %` } }
572+ viewport = { { once : true } }
573+ transition = { { duration : 1.2 , ease : [ 0.16 , 1 , 0.3 , 1 ] , delay : 0.2 } }
574+ className = "h-full rounded-full bg-gradient-to-r from-emerald-500 to-emerald-400 shadow-[0_0_10px_rgba(16,185,129,0.3)]"
575+ />
576+ </ div >
577+ </ div >
578+
579+ { /* Deletions Bar */ }
580+ < div className = "mb-5" >
581+ < div className = "flex justify-between items-center mb-2" >
582+ < span className = "flex items-center gap-1.5 text-xs font-medium text-rose-500" >
583+ < Minus size = { 12 } /> Lines Deleted
584+ </ span >
585+ < motion . span
586+ initial = { { opacity : 0 } }
587+ whileInView = { { opacity : 1 } }
588+ className = "text-sm font-bold text-rose-500"
589+ >
590+ -{ loc . del . toLocaleString ( ) }
591+ </ motion . span >
592+ </ div >
593+ < div className = "w-full h-3 bg-gray-100 dark:bg-[#111] rounded-full overflow-hidden border border-black/5 dark:border-[rgba(255,255,255,0.04)]" >
594+ < motion . div
595+ initial = { { width : 0 } }
596+ whileInView = { { width : `${ ( loc . del / maxDel ) * 100 } %` } }
597+ viewport = { { once : true } }
598+ transition = { { duration : 1.2 , ease : [ 0.16 , 1 , 0.3 , 1 ] , delay : 0.4 } }
599+ className = "h-full rounded-full bg-gradient-to-r from-rose-500 to-rose-400 shadow-[0_0_10px_rgba(244,63,94,0.3)]"
600+ />
601+ </ div >
602+ </ div >
603+
604+ { /* Net Impact */ }
605+ < div className = "flex items-center justify-between p-3 rounded-xl bg-gray-50 dark:bg-[#111] border border-black/5 dark:border-[rgba(255,255,255,0.06)]" >
606+ < span className = "text-[9px] font-medium text-[#A1A1AA] uppercase tracking-widest" >
607+ Net Impact
608+ </ span >
609+ < motion . span
610+ initial = { { scale : 0 } }
611+ whileInView = { { scale : 1 } }
612+ viewport = { { once : true } }
613+ transition = { { type : 'spring' , stiffness : 300 , damping : 20 , delay : 0.6 } }
614+ className = { `text-lg font-black tracking-tight ${ loc . net >= 0 ? 'text-emerald-500' : 'text-rose-500' } ` }
615+ >
616+ { loc . net >= 0 ? '+' : '' }
617+ { loc . net . toLocaleString ( ) }
618+ </ motion . span >
619+ </ div >
620+ </ motion . div >
621+ ) ) }
622+ </ div >
623+ </ div >
624+ ) ;
625+ }
626+
501627/* ── main component ───────────────────────────────────────────────────── */
502628
503629export default function CompareClient ( ) {
@@ -768,12 +894,27 @@ export default function CompareClient() {
768894 valueA = { d1 . profile . stats . followers }
769895 valueB = { d2 . profile . stats . followers }
770896 />
897+ < StatBattle
898+ label = "Pull Requests"
899+ icon = { GitPullRequest }
900+ valueA = { d1 . stats . totalPRs || 0 }
901+ valueB = { d2 . stats . totalPRs || 0 }
902+ />
903+ < StatBattle
904+ label = "Issues"
905+ icon = { CircleDot }
906+ valueA = { d1 . stats . totalIssues || 0 }
907+ valueB = { d2 . stats . totalIssues || 0 }
908+ />
771909 </ div >
772910 </ div >
773911
774912 { /* Coding Habits Showdown */ }
775913 < CodingHabitShowdown user1 = { d1 } user2 = { d2 } />
776914
915+ { /* Code Volume Showdown */ }
916+ < CodeVolumeShowdown user1 = { d1 } user2 = { d2 } />
917+
777918 { /* Language Comparison */ }
778919 < LanguageComparison
779920 langsA = { d1 . languages }
0 commit comments