Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions app/components/Discord/Card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ function hideOnError(e: Event) {
</foreignObject>
<circle cx="61" cy="61" r="8" :fill="statusColor(status)" />
</svg>
<img v-if="decoration" :src="decoration"
<NuxtImg v-if="decoration" :src="decoration"
class="pointer-events-none absolute left-1/2 top-1/2 h-[102px] w-[102px] max-w-none -translate-x-1/2 -translate-y-1/2"
@error="hideOnError" />
</div>
</div>

<!-- Badges (top-right of body) -->
<div class="flex h-12 items-center justify-end gap-1">
<img v-for="badge in badges" :key="badge.label" :src="badge.src" :alt="badge.label" :title="badge.label"
<NuxtImg v-for="badge in badges" :key="badge.label" :src="badge.src" :alt="badge.label" :title="badge.label"
class="h-[22px] w-[22px]" draggable="false" @error="hideOnError" />
</div>
</div>
Expand All @@ -115,7 +115,7 @@ function hideOnError(e: Event) {
<div v-else class="h-5 w-28 animate-pulse rounded bg-zinc-700" />
<span v-if="guildTag"
class="flex items-center gap-1 rounded bg-zinc-800 px-1.5 py-0.5 text-xs font-semibold text-slate-200">
<img v-if="guildBadge" :src="guildBadge" class="h-3.5 w-3.5" draggable="false" @error="hideOnError" />
<NuxtImg v-if="guildBadge" :src="guildBadge" class="h-3.5 w-3.5" draggable="false" @error="hideOnError" />
{{ guildTag }}
</span>
</div>
Expand All @@ -126,7 +126,7 @@ function hideOnError(e: Event) {
<div class="px-4 pb-4 pt-2 text-sm">
<!-- Custom status -->
<div v-if="customStatus?.state" class="flex items-center gap-1.5 pb-2.5">
<img v-if="customEmoji" :src="customEmoji" class="h-5 w-5" draggable="false" @error="hideOnError" />
<NuxtImg v-if="customEmoji" :src="customEmoji" class="h-5 w-5" draggable="false" @error="hideOnError" />
<span>{{ customStatus.state }}</span>
</div>

Expand All @@ -151,7 +151,7 @@ function hideOnError(e: Event) {
<template v-if="item.kind === 'spotify'">
<NuxtLink external :to="`https://open.spotify.com/track/${item.spotify.track_id}`"
class="shrink-0">
<img :src="item.spotify.album_art_url" class="h-14 w-14 rounded" loading="lazy" draggable="false" />
<NuxtImg :src="item.spotify.album_art_url" class="h-14 w-14 rounded" loading="lazy" draggable="false" />
</NuxtLink>
<div class="min-w-0 flex-1">
<p class="flex items-center gap-1 text-[0.7rem] font-semibold uppercase tracking-wide text-green-400">
Expand All @@ -171,7 +171,7 @@ function hideOnError(e: Event) {

<!-- Game / rich presence -->
<template v-else>
<img :src="activityImage(item.activity)" :alt="item.activity.name"
<NuxtImg :src="activityImage(item.activity)" :alt="item.activity.name"
class="h-14 w-14 shrink-0 rounded object-cover" loading="lazy" draggable="false" @error="hideOnError" />
<div class="min-w-0 flex-1">
<p class="text-[0.7rem] font-semibold uppercase tracking-wide text-primary-400">
Expand All @@ -193,7 +193,7 @@ function hideOnError(e: Event) {
<div class="flex flex-col gap-0.5 text-slate-300">
<p v-for="(line, i) in bio" :key="i" class="flex flex-wrap items-center gap-x-1 leading-6">
<template v-for="(token, j) in line" :key="j">
<img v-if="token.type === 'emoji'" :src="token.src" :alt="token.name"
<NuxtImg v-if="token.type === 'emoji'" :src="token.src" :alt="token.name"
class="inline-block h-5 w-5 align-text-bottom" draggable="false" @error="hideOnError" />
<ULink v-else-if="token.type === 'link'" :to="token.href" external
inactive-class="text-primary-400 hover:underline">{{ token.value }}</ULink>
Expand Down
2 changes: 1 addition & 1 deletion app/components/Games/Banner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const open = ref(false)
<!-- Art is scoped to this section so opening the drawer below doesn't
rescale or re-crop it. -->
<div class="relative flex flex-1 flex-col">
<img v-if="background" :src="background" alt="" loading="lazy"
<NuxtImg v-if="background" :src="background" alt="" loading="lazy"
class="absolute inset-0 h-full w-full object-cover object-[center_25%] opacity-60" />
<div class="absolute inset-0 bg-gradient-to-r from-black/90 via-black/20 to-black/70" />

Expand Down
10 changes: 5 additions & 5 deletions app/components/Games/OverwatchCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const owValue = 'font-overwatch text-3xl font-semibold italic leading-none'
<template>
<GamesBanner title="Overwatch" icon="i-ph-crosshair" :background="p.namecard">
<template #logo>
<img src="https://blz-contentstack-images.akamaized.net/v3/assets/blt2477dcaf4ebd440c/blt12c582d9d58631b9/69d573c3c714e07b0dc39b07/overwatch_logo.png"
<NuxtImg src="https://blz-contentstack-images.akamaized.net/v3/assets/blt2477dcaf4ebd440c/blt12c582d9d58631b9/69d573c3c714e07b0dc39b07/overwatch_logo.png"
alt="Overwatch" class="w-40 md:w-36" />
</template>

Expand All @@ -26,7 +26,7 @@ const owValue = 'font-overwatch text-3xl font-semibold italic leading-none'
<UTooltip v-if="p.endorsement_frame" :text="`Endorsement level ${p.endorsement}`" arrow>
<Transition appear enter-from-class="scale-0 -rotate-90 opacity-0"
enter-active-class="transition duration-700 ease-out">
<img :src="p.endorsement_frame" :alt="`Endorsement level ${p.endorsement}`"
<NuxtImg :src="p.endorsement_frame" :alt="`Endorsement level ${p.endorsement}`"
class="h-6 w-6 cursor-help transition-transform duration-300 hover:rotate-12 hover:scale-125" />
</Transition>
</UTooltip>
Expand All @@ -43,10 +43,10 @@ const owValue = 'font-overwatch text-3xl font-semibold italic leading-none'
<UBadge v-for="r in p.ranks" :key="r.role" color="neutral" variant="outline" size="lg"
class="gap-2 rounded border-white/10 bg-black/40 px-2 py-1 ring-white/10 backdrop-blur">
<template #leading>
<img :src="r.role_icon" :alt="r.role" class="h-4 w-4" />
<NuxtImg :src="r.role_icon" :alt="r.role" class="h-4 w-4" />
</template>
<img :src="r.rank_icon" :alt="r.division" :title="`${cap(r.division)} ${r.tier}`" class="h-7 w-7" />
<img v-if="r.tier_icon" :src="r.tier_icon" :alt="`Tier ${r.tier}`" class="h-6 w-auto" />
<NuxtImg :src="r.rank_icon" :alt="r.division" :title="`${cap(r.division)} ${r.tier}`" class="h-7 w-7" />
<NuxtImg v-if="r.tier_icon" :src="r.tier_icon" :alt="`Tier ${r.tier}`" class="h-6 w-auto" />
<span v-else class="font-overwatch text-xl uppercase italic leading-none text-white">{{ cap(r.division) }} {{ r.tier }}</span>
</UBadge>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/components/Games/WowCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const classBackground = computed(() =>
<div v-if="c.flavor === 'retail'" role="img" aria-label="World of Warcraft: Midnight"
class="h-20 w-26 bg-no-repeat md:h-28 md:w-36"
:style="{ backgroundImage: `url(${MIDNIGHT_LOGO})`, backgroundSize: '208%', backgroundPosition: '50% 90%' }" />
<img v-else :src="CLASSIC_LOGO" alt="World of Warcraft Classic" class="w-28 md:w-36" />
<NuxtImg v-else :src="CLASSIC_LOGO" alt="World of Warcraft Classic" class="w-28 md:w-36" />
</template>

<template #identity>
Expand All @@ -62,7 +62,7 @@ const classBackground = computed(() =>
<UAvatar :src="c.media.avatar" :alt="c.name"
class="size-16 border-2 border-(--class-color) bg-black" />
<template #content>
<img :src="c.spec_icon" :alt="c.spec" :title="c.spec" class="size-full" />
<NuxtImg :src="c.spec_icon" :alt="c.spec" :title="c.spec" class="size-full" />
</template>
</UChip>
<div class="min-w-0">
Expand Down
2 changes: 1 addition & 1 deletion app/components/OgImage/Default.satori.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ withDefaults(defineProps<{
<div style="display:flex;align-items:center;font-size:26px;color:#a855f7;letter-spacing:2px;text-transform:uppercase;">
{{ badge }}
</div>
<div style="display:flex;font-size:84px;font-weight:800;line-height:1.05;margin-top:24px;max-width:920px;">
<div style="display:flex;font-size:84px;font-weight:700;line-height:1.05;margin-top:24px;max-width:920px;">
{{ title }}
</div>
<div style="display:flex;font-size:36px;color:#cbd5e1;margin-top:24px;max-width:900px;">
Expand Down
6 changes: 0 additions & 6 deletions app/pages/gateway.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ useHead({
content: 'This hostname is resolving to a gateway endpoint with no configured service.',
}],
})

defineOgImage('Default', {
title: 'No Service Configured',
description: 'This hostname resolves to a gateway endpoint with no service behind it.',
badge: 'Gateway',
})
</script>

<template>
Expand Down
33 changes: 15 additions & 18 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import app from './app/config/app'

export default defineNuxtConfig({
devtools: { enabled: true },
future: { compatibilityVersion: 4 },
app,

modules: [
Expand All @@ -27,8 +26,7 @@ export default defineNuxtConfig({

// Allow optimising remote avatars/banners through <NuxtImg>.
image: {
// Passthrough provider: the Discord/Spotify images are already sized by
// their CDNs, and this avoids bundling `sharp` (which can't run on CF Workers).
// Passthrough provider: the remote images are already sized by their CDNs.
provider: 'none',
domains: [
'cdn.discordapp.com',
Expand Down Expand Up @@ -56,8 +54,8 @@ export default defineNuxtConfig({
},
},

// Pre-render OG images at build (zeroRuntime) instead of rendering them on the
// edge — the runtime renderer (satori + native resvg) can't run on CF Workers.
// Every page with an OG image is prerendered, so render them at build and keep
// satori/resvg out of the server bundle.
ogImage: {
zeroRuntime: true,
},
Expand All @@ -70,18 +68,14 @@ export default defineNuxtConfig({
resend: { apiKey: '' },
contactTo: 'noc@ok8.sh',
contactFrom: 'Portfolio <onboarding@resend.dev>',
// Turnstile keys are added by @nuxtjs/turnstile; set them at runtime with
// NUXT_PUBLIC_TURNSTILE_SITE_KEY and NUXT_TURNSTILE_SECRET_KEY.
public: {
userId: '201077739589992448',
github: 'kashalls'
}
},

// Cloudflare Turnstile. In dev this auto-uses the always-pass test keys; for
// production set turnstile.siteKey here and NUXT_TURNSTILE_SECRET_KEY in env.
turnstile: {
siteKey: process.env.NUXT_TURNSTILE_SITE_KEY,
},

content: {
experimental: {
// Node's built-in node:sqlite (Node >= 22.5), so the distroless
Expand All @@ -92,6 +86,15 @@ export default defineNuxtConfig({

css: ['~/assets/css/main.css'],

sourcemap: { server: false },

routeRules: {
// Renders the requesting hostname, so it must be rendered per request.
'/gateway': { prerender: false },
// Local fonts aren't content-hashed, so cache them without `immutable`.
'/fonts/**': { headers: { 'cache-control': 'public, max-age=2592000' } },
},

colorMode: {
preference: 'dark',
},
Expand All @@ -100,20 +103,14 @@ export default defineNuxtConfig({
// Prerender the static routes so their OG images are generated at build
// (required by ogImage.zeroRuntime). Dynamic client data still hydrates.
prerender: {
routes: ['/', '/gateway', '/blog'],
routes: ['/', '/blog'],
// Follow links from /blog so every post (and its OG image) prerenders.
crawlLinks: true,
},
// Persist the cache to disk in dev so it survives restarts (fewer GitHub calls).
devStorage: {
cache: { driver: 'fs', base: './.cache' },
},
// For durable caching across serverless cold starts in production, point the
// `cache` mount at a persistent store. On Cloudflare Pages, bind a KV
// namespace named CACHE and uncomment:
// storage: {
// cache: { driver: 'cloudflareKVBinding', binding: 'CACHE' },
// },
},

fonts: {
Expand Down
Binary file removed public/_fonts/PPNeueMachinaInktrap-Light.otf
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaInktrap-LightItalic.otf
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaInktrap-Regular.otf
Binary file not shown.
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaInktrap-Ultrabold.otf
Binary file not shown.
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaPlain-Light.otf
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaPlain-LightItalic.otf
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaPlain-Regular.otf
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaPlain-RegularItalic.otf
Binary file not shown.
Binary file removed public/_fonts/PPNeueMachinaPlain-Ultrabold.otf
Binary file not shown.
Binary file not shown.
Binary file added public/fonts/PPNeueMachinaInktrap-Light.woff2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added public/fonts/PPNeueMachinaInktrap-Ultrabold.woff2
Binary file not shown.
Binary file not shown.
Binary file added public/fonts/PPNeueMachinaPlain-Light.woff2
Binary file not shown.
Binary file added public/fonts/PPNeueMachinaPlain-LightItalic.woff2
Binary file not shown.
Binary file added public/fonts/PPNeueMachinaPlain-Regular.woff2
Binary file not shown.
Binary file not shown.
Binary file added public/fonts/PPNeueMachinaPlain-Ultrabold.woff2
Binary file not shown.
Binary file not shown.
Loading