@@ -19,6 +19,9 @@ import {
1919 Moon ,
2020 Sun ,
2121 Coffee ,
22+ Plus ,
23+ Minus ,
24+ Code2 ,
2225} from 'lucide-react' ;
2326
2427/* ── types ────────────────────────────────────────────────────────────── */
@@ -57,6 +60,8 @@ interface ActivityData {
5760 date : string ;
5861 count : number ;
5962 intensity : 0 | 1 | 2 | 3 | 4 ;
63+ locAdditions ?: number ;
64+ locDeletions ?: number ;
6065}
6166
6267interface CompareUserData {
@@ -498,6 +503,123 @@ function CodingHabitShowdown({ user1, user2 }: { user1: CompareUserData; user2:
498503 ) ;
499504}
500505
506+ /* ── helper: code volume showdown ─────────────────────────────────────── */
507+
508+ function CodeVolumeShowdown ( { user1, user2 } : { user1 : CompareUserData ; user2 : CompareUserData } ) {
509+ const calcLoC = ( activity : ActivityData [ ] ) => {
510+ let add = 0 ,
511+ del = 0 ;
512+ activity . forEach ( ( d ) => {
513+ add += d . locAdditions || 0 ;
514+ del += d . locDeletions || 0 ;
515+ } ) ;
516+ return { add, del, net : add - del } ;
517+ } ;
518+
519+ const loc1 = calcLoC ( user1 . activity ) ;
520+ const loc2 = calcLoC ( user2 . activity ) ;
521+
522+ const maxAdd = Math . max ( loc1 . add , loc2 . add , 1 ) ;
523+ const maxDel = Math . max ( loc1 . del , loc2 . del , 1 ) ;
524+
525+ const users = [
526+ { username : user1 . profile . username , loc : loc1 , side : 'left' as const } ,
527+ { username : user2 . profile . username , loc : loc2 , side : 'right' as const } ,
528+ ] ;
529+
530+ return (
531+ < div >
532+ < h2 className = "text-xs text-[#A1A1AA] uppercase tracking-widest font-medium mb-4 flex items-center gap-2" >
533+ < Code2 size = { 14 } className = "text-violet-400" />
534+ Code Volume
535+ </ h2 >
536+ < div className = "grid grid-cols-1 md:grid-cols-2 gap-6" >
537+ { users . map ( ( { username, loc, side } ) => (
538+ < motion . div
539+ key = { side }
540+ initial = { { opacity : 0 , x : side === 'left' ? - 20 : 20 } }
541+ whileInView = { { opacity : 1 , x : 0 } }
542+ viewport = { { once : true } }
543+ whileHover = { { scale : 1.01 } }
544+ 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"
545+ >
546+ < h4 className = "text-xs font-bold uppercase tracking-widest text-[#A1A1AA] mb-5" >
547+ @{ username }
548+ </ h4 >
549+
550+ { /* Additions Bar */ }
551+ < div className = "mb-4" >
552+ < div className = "flex justify-between items-center mb-2" >
553+ < span className = "flex items-center gap-1.5 text-xs font-medium text-emerald-500" >
554+ < Plus size = { 12 } /> Lines Added
555+ </ span >
556+ < motion . span
557+ initial = { { opacity : 0 } }
558+ whileInView = { { opacity : 1 } }
559+ className = "text-sm font-bold text-emerald-500"
560+ >
561+ +{ loc . add . toLocaleString ( ) }
562+ </ motion . span >
563+ </ div >
564+ < 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)]" >
565+ < motion . div
566+ initial = { { width : 0 } }
567+ whileInView = { { width : `${ ( loc . add / maxAdd ) * 100 } %` } }
568+ viewport = { { once : true } }
569+ transition = { { duration : 1.2 , ease : [ 0.16 , 1 , 0.3 , 1 ] , delay : 0.2 } }
570+ 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)]"
571+ />
572+ </ div >
573+ </ div >
574+
575+ { /* Deletions Bar */ }
576+ < div className = "mb-5" >
577+ < div className = "flex justify-between items-center mb-2" >
578+ < span className = "flex items-center gap-1.5 text-xs font-medium text-rose-500" >
579+ < Minus size = { 12 } /> Lines Deleted
580+ </ span >
581+ < motion . span
582+ initial = { { opacity : 0 } }
583+ whileInView = { { opacity : 1 } }
584+ className = "text-sm font-bold text-rose-500"
585+ >
586+ -{ loc . del . toLocaleString ( ) }
587+ </ motion . span >
588+ </ div >
589+ < 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)]" >
590+ < motion . div
591+ initial = { { width : 0 } }
592+ whileInView = { { width : `${ ( loc . del / maxDel ) * 100 } %` } }
593+ viewport = { { once : true } }
594+ transition = { { duration : 1.2 , ease : [ 0.16 , 1 , 0.3 , 1 ] , delay : 0.4 } }
595+ 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)]"
596+ />
597+ </ div >
598+ </ div >
599+
600+ { /* Net Impact */ }
601+ < 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)]" >
602+ < span className = "text-[9px] font-medium text-[#A1A1AA] uppercase tracking-widest" >
603+ Net Impact
604+ </ span >
605+ < motion . span
606+ initial = { { scale : 0 } }
607+ whileInView = { { scale : 1 } }
608+ viewport = { { once : true } }
609+ transition = { { type : 'spring' , stiffness : 300 , damping : 20 , delay : 0.6 } }
610+ className = { `text-lg font-black tracking-tight ${ loc . net >= 0 ? 'text-emerald-500' : 'text-rose-500' } ` }
611+ >
612+ { loc . net >= 0 ? '+' : '' }
613+ { loc . net . toLocaleString ( ) }
614+ </ motion . span >
615+ </ div >
616+ </ motion . div >
617+ ) ) }
618+ </ div >
619+ </ div >
620+ ) ;
621+ }
622+
501623/* ── main component ───────────────────────────────────────────────────── */
502624
503625export default function CompareClient ( ) {
@@ -774,6 +896,9 @@ export default function CompareClient() {
774896 { /* Coding Habits Showdown */ }
775897 < CodingHabitShowdown user1 = { d1 } user2 = { d2 } />
776898
899+ { /* Code Volume Showdown */ }
900+ < CodeVolumeShowdown user1 = { d1 } user2 = { d2 } />
901+
777902 { /* Language Comparison */ }
778903 < LanguageComparison
779904 langsA = { d1 . languages }
0 commit comments