Skip to content

Commit 7f6cb5c

Browse files
feat(compare): add Code Volume (LoC +/-) animated showdown section
1 parent be3979a commit 7f6cb5c

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

app/compare/CompareClient.tsx

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

6267
interface 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

503625
export 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

Comments
 (0)