11<script setup lang="ts">
2- import { computed } from ' vue'
2+ import { computed , ref } from ' vue'
3+ import { encodePackageName } from ' #shared/utils/npm'
34
45const props = withDefaults (
56 defineProps <{
@@ -25,23 +26,79 @@ const displayPackages = computed(() => {
2526 : raw
2627 return list .slice (0 , 4 )
2728})
29+
30+ interface PkgStats {
31+ name: string
32+ downloads: number
33+ version: string
34+ color: string
35+ }
36+
37+ const stats = ref <PkgStats []>([])
38+
39+ try {
40+ const results = await Promise .all (
41+ displayPackages .value .map (async (name , index ) => {
42+ const encoded = encodePackageName (name )
43+ const [dlData, pkgData] = await Promise .all ([
44+ $fetch <{ downloads: number }>(
45+ ` https://api.npmjs.org/downloads/point/last-week/${encoded } ` ,
46+ ).catch (() => null ),
47+ $fetch <{ ' dist-tags' ? : { latest? : string } }>(
48+ ` https://registry.npmjs.org/${encoded } ` ,
49+ { headers: { Accept: ' application/vnd.npm.install-v1+json' } },
50+ ).catch (() => null ),
51+ ])
52+ return {
53+ name ,
54+ downloads: dlData ?.downloads ?? 0 ,
55+ version: pkgData ?.[' dist-tags' ]?.latest ?? ' ' ,
56+ color: ACCENT_COLORS [index % ACCENT_COLORS .length ]! ,
57+ }
58+ }),
59+ )
60+ stats .value = results
61+ } catch {
62+ stats .value = displayPackages .value .map ((name , index ) => ({
63+ name ,
64+ downloads: 0 ,
65+ version: ' ' ,
66+ color: ACCENT_COLORS [index % ACCENT_COLORS .length ]! ,
67+ }))
68+ }
69+
70+ const maxDownloads = computed (() => Math .max (... stats .value .map (s => s .downloads ), 1 ))
71+
72+ function formatDownloads(n : number ): string {
73+ if (n === 0 ) return ' —'
74+ return Intl .NumberFormat (' en' , {
75+ notation: ' compact' ,
76+ maximumFractionDigits: 1 ,
77+ }).format (n )
78+ }
79+
80+ // Bar width as percentage string (max 100%)
81+ function barPct(downloads : number ): string {
82+ const pct = (downloads / maxDownloads .value ) * 100
83+ return ` ${Math .max (pct , 5 )}% `
84+ }
2885 </script >
2986
3087<template >
3188 <div
3289 class =" h-full w-full flex flex-col justify-center px-20 bg-[#050505] text-[#fafafa] relative overflow-hidden"
3390 style =" font-family : ' Geist Mono' , sans-serif "
3491 >
35- <div class =" relative z-10 flex flex-col gap-6 " >
36- <!-- Icon + title row (same pattern as Default/Package) -->
92+ <div class =" relative z-10 flex flex-col gap-5 " >
93+ <!-- Icon + title row -->
3794 <div class =" flex items-start gap-4" >
3895 <div
39- class =" flex items-center justify-center w-16 h-16 p-3.5 rounded-xl shadow-lg bg-gradient-to-tr from-[#3b82f6]"
96+ class =" flex items-center justify-center w-14 h-14 p-3 rounded-xl shadow-lg bg-gradient-to-tr from-[#3b82f6]"
4097 :style =" { backgroundColor: primaryColor }"
4198 >
4299 <svg
43- width =" 36 "
44- height =" 36 "
100+ width =" 32 "
101+ height =" 32 "
45102 viewBox =" 0 0 24 24"
46103 fill =" none"
47104 stroke =" white"
@@ -56,35 +113,58 @@ const displayPackages = computed(() => {
56113 </svg >
57114 </div >
58115
59- <h1 class =" text-8xl font-bold" >
116+ <h1 class =" text-7xl font-bold tracking-tight " >
60117 <span
61118 class =" opacity-80 tracking-[-0.1em]"
62119 :style =" { color: primaryColor }"
63- style =" margin-left : -1rem ; margin-right : 0.5rem "
64- >./</span
65- >compare
120+ style =" margin-right : 0.25rem "
121+ >./</span >compare
66122 </h1 >
67123 </div >
68124
69- <!-- Package names as badges (same badge style as Default/Package) -->
70- <div
71- class =" flex flex-wrap items-center gap-x-3 gap-y-3 text-4xl text-[#a3a3a3]"
72- style =" font-family : ' Geist' , sans-serif "
73- >
74- <template v-for =" (pkg , index ) in displayPackages " :key =" pkg " >
75- <span
76- class =" px-3 py-1 rounded-lg border font-normal"
125+ <!-- Bar chart rows -->
126+ <div class =" flex flex-col gap-2" >
127+ <div
128+ v-for =" pkg in stats"
129+ :key =" pkg.name"
130+ class =" flex flex-col gap-1"
131+ >
132+ <!-- Label row: name + downloads + version -->
133+ <div
134+ class =" flex items-center gap-3"
135+ style =" font-family : ' Geist' , sans-serif "
136+ >
137+ <span
138+ class =" text-2xl font-semibold tracking-tight"
139+ :style =" { color: pkg.color }"
140+ >
141+ {{ pkg.name }}
142+ </span >
143+ <span class =" text-xl text-[#737373]" >
144+ {{ formatDownloads(pkg.downloads) }}/wk
145+ </span >
146+ <span
147+ v-if =" pkg.version"
148+ class =" text-lg px-2 py-0.5 rounded-md border"
149+ :style =" {
150+ color: pkg.color,
151+ backgroundColor: pkg.color + '10',
152+ borderColor: pkg.color + '30',
153+ }"
154+ >
155+ {{ pkg.version }}
156+ </span >
157+ </div >
158+
159+ <!-- Bar -->
160+ <div
161+ class =" h-6 rounded-md"
77162 :style =" {
78- color: ACCENT_COLORS[index % ACCENT_COLORS.length],
79- backgroundColor: ACCENT_COLORS[index % ACCENT_COLORS.length] + '10',
80- borderColor: ACCENT_COLORS[index % ACCENT_COLORS.length] + '30',
81- boxShadow: `0 0 20px ${ACCENT_COLORS[index % ACCENT_COLORS.length]}25`,
163+ width: barPct(pkg.downloads),
164+ background: `linear-gradient(90deg, ${pkg.color}50, ${pkg.color}20)`,
82165 }"
83- >
84- {{ pkg }}
85- </span >
86- <span v-if =" index < displayPackages.length - 1" > vs </span >
87- </template >
166+ />
167+ </div >
88168 </div >
89169 </div >
90170
0 commit comments