@@ -3,6 +3,8 @@ type ActivityData = { year: number; repos: number }
33type CommitActivityData = { year : number ; commits : number }
44type RepoStarsData = { name : string ; stars : number ; language : string }
55
6+ import { useInView } from './useInView'
7+
68type Stats = {
79 metrics : {
810 totalRepos : number
@@ -34,14 +36,14 @@ const ORE_COLORS = [
3436
3537const BAR_WIDTH = 120
3638
37- function PixelBar ( { pct, colorClass } : { pct : number ; colorClass : string } ) {
39+ function PixelBar ( { pct, colorClass, animate , delay } : { pct : number ; colorClass : string ; animate ?: boolean ; delay ?: number } ) {
3840 const filled = Math . max ( 2 , Math . round ( ( pct / 100 ) * BAR_WIDTH ) )
3941 return (
4042 < div className = "h-3 relative" style = { { width : BAR_WIDTH } } >
4143 < div className = "absolute inset-0 bg-[#3b3b3b] border border-[#2b2b2b]" />
4244 < div
43- className = { `absolute top-0 left-0 h-full ${ colorClass } border-r border-[#2b2b2b]` }
44- style = { { width : filled , imageRendering : 'pixelated' } }
45+ className = { `absolute top-0 left-0 h-full ${ colorClass } border-r border-[#2b2b2b] ${ animate ? 'animate-mc-bar-fill' : '' } ` }
46+ style = { { width : filled , imageRendering : 'pixelated' , animationDelay : delay ? ` ${ delay } s` : undefined } }
4547 />
4648 </ div >
4749 )
@@ -61,6 +63,7 @@ function MetricSlot({ label, value }: { label: string; value: number }) {
6163}
6264
6365export default function StatsSection ( { stats } : { stats : Stats | null } ) {
66+ const { ref, visible } = useInView ( )
6467 if ( ! stats ) return null
6568
6669 const { metrics, languageDistribution, activityByYear, commitActivityByYear, topReposByStars } = stats
@@ -73,7 +76,7 @@ export default function StatsSection({ stats }: { stats: Stats | null }) {
7376
7477 return (
7578 < section id = "stats" className = "bg-[#1a1a2e] py-12" aria-labelledby = "stats-title" >
76- < div className = "mx-auto max-w-4xl px-6 space-y-8" >
79+ < div ref = { ref } className = "mx-auto max-w-4xl px-6 space-y-8" >
7780 { /* Header */ }
7881 < div >
7982 < p className = "text-xs uppercase tracking-widest text-[#5c7a29] mb-1" > 📊 Statistics</ p >
@@ -84,10 +87,16 @@ export default function StatsSection({ stats }: { stats: Stats | null }) {
8487
8588 { /* Metrics — inventory hotbar style */ }
8689 < div className = "grid grid-cols-2 gap-2 sm:grid-cols-4" >
87- < MetricSlot label = "Repos" value = { metrics . totalRepos } />
88- < MetricSlot label = "Stars" value = { metrics . totalStars } />
89- < MetricSlot label = "Forks" value = { metrics . totalForks } />
90- < MetricSlot label = "Followers" value = { metrics . followers } />
90+ { [
91+ { label : 'Repos' , value : metrics . totalRepos } ,
92+ { label : 'Stars' , value : metrics . totalStars } ,
93+ { label : 'Forks' , value : metrics . totalForks } ,
94+ { label : 'Followers' , value : metrics . followers } ,
95+ ] . map ( ( m , i ) => (
96+ < div key = { m . label } className = { visible ? 'animate-mc-place' : 'opacity-0' } style = { { animationDelay : `${ i * 0.1 } s` } } >
97+ < MetricSlot label = { m . label } value = { m . value } />
98+ </ div >
99+ ) ) }
91100 </ div >
92101
93102 { /* Charts in 2-column grid */ }
@@ -103,7 +112,7 @@ export default function StatsSection({ stats }: { stats: Stats | null }) {
103112 { languageDistribution . slice ( 0 , 8 ) . map ( ( lang , i ) => (
104113 < div key = { lang . language } className = "flex items-center gap-2 text-xs" >
105114 < span className = "w-20 shrink-0 truncate text-[#d4d4d4] font-bold" > { lang . language } </ span >
106- < PixelBar pct = { lang . percentage } colorClass = { ORE_COLORS [ i % ORE_COLORS . length ] } />
115+ < PixelBar pct = { lang . percentage } colorClass = { ORE_COLORS [ i % ORE_COLORS . length ] } animate = { visible } delay = { i * 0.1 } />
107116 < span className = "w-8 shrink-0 text-right text-[#a0a0a0]" > { lang . percentage } %</ span >
108117 </ div >
109118 ) ) }
@@ -123,7 +132,7 @@ export default function StatsSection({ stats }: { stats: Stats | null }) {
123132 { activityByYear . map ( ( row ) => (
124133 < div key = { row . year } className = "flex items-center gap-2 text-xs" >
125134 < span className = "w-10 shrink-0 text-[#d4d4d4] font-bold" > { row . year } </ span >
126- < PixelBar pct = { ( row . repos / maxRepos ) * 100 } colorClass = "bg-[#5CC0D0]" />
135+ < PixelBar pct = { ( row . repos / maxRepos ) * 100 } colorClass = "bg-[#5CC0D0]" animate = { visible } delay = { 0.2 } />
127136 < span className = "w-6 shrink-0 text-right text-[#a0a0a0]" > { row . repos } </ span >
128137 </ div >
129138 ) ) }
@@ -143,7 +152,7 @@ export default function StatsSection({ stats }: { stats: Stats | null }) {
143152 { commitActivityByYear . map ( ( row ) => (
144153 < div key = { row . year } className = "flex items-center gap-2 text-xs" >
145154 < span className = "w-10 shrink-0 text-[#d4d4d4] font-bold" > { row . year } </ span >
146- < PixelBar pct = { ( row . commits / maxCommits ) * 100 } colorClass = "bg-[#5c7a29]" />
155+ < PixelBar pct = { ( row . commits / maxCommits ) * 100 } colorClass = "bg-[#5c7a29]" animate = { visible } delay = { 0.2 } />
147156 < span className = "w-10 shrink-0 text-right text-[#a0a0a0]" > { row . commits } </ span >
148157 </ div >
149158 ) ) }
@@ -163,7 +172,7 @@ export default function StatsSection({ stats }: { stats: Stats | null }) {
163172 { topReposByStars . map ( ( repo ) => (
164173 < div key = { repo . name } className = "flex items-center gap-2 text-xs" >
165174 < span className = "w-24 shrink-0 truncate text-[#d4d4d4] font-bold" > { repo . name } </ span >
166- < PixelBar pct = { ( repo . stars / maxStars ) * 100 } colorClass = "bg-[#FFAA00]" />
175+ < PixelBar pct = { ( repo . stars / maxStars ) * 100 } colorClass = "bg-[#FFAA00]" animate = { visible } delay = { 0.2 } />
167176 < span className = "shrink-0 text-[#ffaa00]" > ★{ repo . stars } </ span >
168177 </ div >
169178 ) ) }
0 commit comments