Skip to content

Commit 9ef1401

Browse files
feat: better OG images (#462)
1 parent db31696 commit 9ef1401

12 files changed

Lines changed: 190 additions & 19 deletions

File tree

app/components/OgImage/Default.vue

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,81 @@
1+
<script setup lang="ts">
2+
interface Props {
3+
primaryColor?: string
4+
title?: string
5+
description?: string
6+
}
7+
8+
const props = withDefaults(defineProps<Props>(), {
9+
primaryColor: '#60a5fa',
10+
title: 'npmx',
11+
description: 'A better browser for the **npm registry**',
12+
})
13+
</script>
14+
115
<template>
2-
<div class="h-full w-full flex flex-col justify-center items-center bg-[#0a0a0a] text-[#fafafa]">
3-
<h1 class="text-6xl font-medium tracking-tight" style="font-family: 'Geist Mono'">
4-
<span class="text-fg-subtle" style="letter-spacing: -0.1em">./</span> npmx
5-
</h1>
6-
<h1 class="text-3xl font-medium tracking-tight">a better browser for the npm registry</h1>
16+
<div
17+
class="h-full w-full flex flex-col justify-center px-20 bg-[#050505] text-[#fafafa] relative overflow-hidden"
18+
>
19+
<div class="relative z-10 flex flex-col gap-6">
20+
<div class="flex items-start gap-4">
21+
<div
22+
class="flex items-start justify-center w-16 h-16 rounded-xl bg-gradient-to-tr from-[#3b82f6] shadow-lg"
23+
:style="{ backgroundColor: props.primaryColor }"
24+
>
25+
<svg
26+
width="36"
27+
height="36"
28+
viewBox="0 0 24 24"
29+
fill="none"
30+
stroke="white"
31+
stroke-width="2.5"
32+
stroke-linecap="round"
33+
stroke-linejoin="round"
34+
>
35+
<path d="m7.5 4.27 9 5.15" />
36+
<path
37+
d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"
38+
/>
39+
<path d="m3.3 7 8.7 5 8.7-5" />
40+
<path d="M12 22V12" />
41+
</svg>
42+
</div>
43+
44+
<h1
45+
class="text-8xl font-bold tracking-tighter"
46+
style="font-family: 'Geist Sans', sans-serif"
47+
>
48+
<span class="opacity-80" :style="{ color: props.primaryColor }">./</span>{{ props.title }}
49+
</h1>
50+
</div>
51+
52+
<div
53+
class="flex flex-wrap items-center gap-x-3 text-4xl font-light text-[#a3a3a3]"
54+
style="font-family: 'Geist Sans', sans-serif"
55+
>
56+
<template v-for="(part, index) in props.description.split(/(\*\*.*?\*\*)/)" :key="index">
57+
<span
58+
v-if="part.startsWith('**') && part.endsWith('**')"
59+
class="px-3 py-1 rounded-lg border font-normal"
60+
:style="{
61+
color: props.primaryColor,
62+
backgroundColor: props.primaryColor + '10',
63+
borderColor: props.primaryColor + '30',
64+
boxShadow: `0 0 20px ${props.primaryColor}25`,
65+
}"
66+
>
67+
{{ part.replaceAll('**', '') }}
68+
</span>
69+
<span v-else-if="part.trim() !== ''">
70+
{{ part }}
71+
</span>
72+
</template>
73+
</div>
74+
</div>
775

8-
<p class="absolute bottom-12 text-lg text-[#404040]">npmx.dev</p>
76+
<div
77+
class="absolute -top-32 -right-32 w-[550px] h-[550px] rounded-full blur-3xl"
78+
:style="{ backgroundColor: props.primaryColor + '10' }"
79+
/>
980
</div>
1081
</template>

app/components/OgImage/Package.vue

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,95 @@ withDefaults(
55
version: string
66
downloads?: string
77
license?: string
8+
primaryColor?: string
89
}>(),
910
{
1011
downloads: '',
1112
license: '',
13+
primaryColor: '#60a5fa',
1214
},
1315
)
1416
</script>
1517

1618
<template>
1719
<div
18-
class="h-full w-full flex flex-col justify-center items-center bg-[#0a0a0a] text-[#fafafa]"
19-
style="font-family: 'Geist Mono'"
20+
class="h-full w-full flex flex-col justify-center px-20 bg-[#050505] text-[#fafafa] relative overflow-hidden"
2021
>
21-
<h1 class="text-6xl font-medium tracking-tight">
22-
{{ name }}
23-
</h1>
22+
<div class="relative z-10 flex flex-col gap-6">
23+
<div class="flex items-start gap-4">
24+
<div
25+
class="flex items-start justify-center w-16 h-16 rounded-xl shadow-lg bg-gradient-to-tr from-[#3b82f6]"
26+
:style="{ backgroundColor: primaryColor }"
27+
>
28+
<svg
29+
width="36"
30+
height="36"
31+
viewBox="0 0 24 24"
32+
fill="none"
33+
stroke="white"
34+
stroke-width="2.5"
35+
stroke-linecap="round"
36+
stroke-linejoin="round"
37+
>
38+
<path d="m7.5 4.27 9 5.15" />
39+
<path
40+
d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"
41+
/>
42+
<path d="m3.3 7 8.7 5 8.7-5" />
43+
<path d="M12 22V12" />
44+
</svg>
45+
</div>
2446

25-
<div class="flex items-center gap-6 mt-6 text-xl text-fg-subtle">
26-
<span>v{{ version }}</span>
27-
<span v-if="downloads">{{ downloads }}/wk</span>
28-
<span v-if="license">{{ license }}</span>
47+
<h1
48+
class="text-8xl font-bold tracking-tighter"
49+
style="font-family: 'Geist Sans', sans-serif"
50+
>
51+
<span :style="{ color: primaryColor }" class="opacity-80">./</span>{{ name }}
52+
</h1>
53+
</div>
54+
55+
<div
56+
class="flex items-center gap-3 text-4xl font-light text-[#a3a3a3]"
57+
style="font-family: 'Geist Sans', sans-serif"
58+
>
59+
<span
60+
class="px-3 py-1 rounded-lg border"
61+
:style="{
62+
color: primaryColor,
63+
backgroundColor: primaryColor + '10',
64+
borderColor: primaryColor + '30',
65+
boxShadow: `0 0 20px ${primaryColor}25`,
66+
}"
67+
>
68+
{{ version }}
69+
</span>
70+
<span v-if="downloads">
71+
<span>• {{ downloads }} </span>
72+
<span class="flex items-center gap-0">
73+
<svg
74+
width="30"
75+
height="30"
76+
viewBox="0 0 24 24"
77+
fill="none"
78+
stroke="currentColor"
79+
stroke-width="2"
80+
stroke-linecap="round"
81+
stroke-linejoin="round"
82+
class="text-white/70"
83+
>
84+
<circle cx="12" cy="12" r="10" class="opacity-40" />
85+
<path d="M12 8v8m-3-3l3 3 3-3" />
86+
</svg>
87+
<span>/wk</span>
88+
</span>
89+
</span>
90+
<span v-if="license"> • {{ license }}</span>
91+
</div>
2992
</div>
3093

31-
<p class="absolute bottom-12 text-lg text-[#404040]">npmx.dev</p>
94+
<div
95+
class="absolute -top-32 -right-32 w-[550px] h-[550px] rounded-full blur-3xl"
96+
:style="{ backgroundColor: primaryColor + '10' }"
97+
/>
3298
</div>
3399
</template>

app/pages/@[org].vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ useSeoMeta({
131131
defineOgImageComponent('Default', {
132132
title: () => `@${orgName.value}`,
133133
description: () => (packageCount.value ? `${packageCount.value} packages` : 'npm organization'),
134+
primaryColor: '#60a5fa',
134135
})
135136
</script>
136137

app/pages/[...package].vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ defineOgImageComponent('Package', {
347347
version: () => displayVersion.value?.version ?? '',
348348
downloads: () => (downloads.value ? formatNumber(downloads.value.downloads) : ''),
349349
license: () => pkg.value?.license ?? '',
350+
primaryColor: '#60a5fa',
350351
})
351352
352353
// We're using only @click because it catches touch events and enter hits

app/pages/about.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ useSeoMeta({
1313
})
1414
1515
defineOgImageComponent('Default', {
16-
title: () => $t('about.title'),
17-
description: () => $t('tagline'),
16+
primaryColor: '#60a5fa',
17+
title: 'About npmx',
18+
description: 'A better browser for the **npm registry**',
1819
})
1920
2021
const pmLinks = {

app/pages/code/[...path].vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,12 @@ useSeoMeta({
271271
},
272272
description: () => `Browse source code for ${packageName.value}@${version.value}`,
273273
})
274+
275+
defineOgImageComponent('Default', {
276+
title: () => `${pkg.value?.name ?? 'Package'} - Code`,
277+
description: () => pkg.value?.license ?? '',
278+
primaryColor: '#60a5fa',
279+
})
274280
</script>
275281

276282
<template>

app/pages/docs/[...path].vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ useSeoMeta({
9696
title: () => pageTitle.value,
9797
})
9898
99+
defineOgImageComponent('Default', {
100+
title: () => `${pkg.value?.name ?? 'Package'} - Docs`,
101+
description: () => pkg.value?.license ?? '',
102+
primaryColor: '#60a5fa',
103+
})
104+
99105
const showLoading = computed(() => docsStatus.value === 'pending')
100106
const showEmptyState = computed(() => docsData.value?.status !== 'ok')
101107
</script>

app/pages/index.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ useSeoMeta({
2525
description: () => $t('seo.home.description'),
2626
})
2727
28-
defineOgImageComponent('Default')
28+
defineOgImageComponent('Default', {
29+
primaryColor: '#60a5fa',
30+
title: 'npmx',
31+
description: 'A better browser for the **npm registry**',
32+
})
2933
</script>
3034

3135
<template>

app/pages/search.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ useSeoMeta({
699699
defineOgImageComponent('Default', {
700700
title: 'npmx',
701701
description: () => (query.value ? `Search results for "${query.value}"` : 'Search npm packages'),
702+
primaryColor: '#60a5fa',
702703
})
703704
</script>
704705

app/pages/settings.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ useSeoMeta({
2121
defineOgImageComponent('Default', {
2222
title: () => $t('settings.title'),
2323
description: () => $t('settings.tagline'),
24+
primaryColor: '#60a5fa',
2425
})
2526
</script>
2627

0 commit comments

Comments
 (0)