From a826a13a1118789ccc9e37509a28c2a6f9c993f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 15:19:52 +0000 Subject: [PATCH] Let a project say which edge of its image to keep when cropped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The marquee squares off a landscape screenshot, and a centre crop of the What's the Point launcher throws away the half that identifies it: the wordmark and the call to action sit on the left, and what survives is empty background and a few playing cards. Projects gain an optional `imageAnchor`, one of the nine usual positions, defaulting to centre so nothing else moves. The anchor has to be spelled twice because the two call sites are cropped by different things. The projects page takes the image whole and lets `object-cover` crop it in the browser, which `object-left` steers. The marquee asks @nuxt/image for a 234px square and gets one back already cropped, server-side, where CSS never gets a say — that one needs the position handed to the resize. The first version of this only did the former and changed nothing on the page that prompted it. Corners map to sharp's compass names rather than its `left top` spelling because the modifier ends up in a URL and IPX answers 500 to the space. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UHZiNTwwiniSxbBr9BQdEw --- app/components/landing/Hero.vue | 2 ++ app/pages/projects.vue | 1 + app/utils/projectImage.ts | 51 ++++++++++++++++++++++++++++ content.config.ts | 2 ++ content/projects/whats-the-point.yml | 1 + 5 files changed, 57 insertions(+) create mode 100644 app/utils/projectImage.ts diff --git a/app/components/landing/Hero.vue b/app/components/landing/Hero.vue index 43df183..6b098ed 100644 --- a/app/components/landing/Hero.vue +++ b/app/components/landing/Hero.vue @@ -164,6 +164,8 @@ defineProps<{ width="234" height="234" class="aspect-square object-cover transition-all duration-300 ease-out group-hover:blur-[2px]" + :class="projectImageAnchorClass(project.imageAnchor)" + :modifiers="projectImageAnchorModifiers(project.imageAnchor)" :light="project.image!" :dark="project.imageDark || project.image!" :alt="project.title" diff --git a/app/pages/projects.vue b/app/pages/projects.vue index bd73ada..c457395 100644 --- a/app/pages/projects.vue +++ b/app/pages/projects.vue @@ -107,6 +107,7 @@ defineOgImage('Portfolio', { title, description }) :dark="project.imageDark || project.image!" :alt="project.title" class="object-cover w-full h-48 transition-all duration-300 ease-out group-hover:blur-[2px]" + :class="projectImageAnchorClass(project.imageAnchor)" />
z.object({ @@ -58,6 +59,7 @@ export default defineContentConfig({ description: z.string().nonempty(), image: z.string().editor({ input: 'media' }).optional(), imageDark: z.string().editor({ input: 'media' }).optional(), + imageAnchor: z.enum(projectImageAnchors).optional(), url: z.string().nonempty(), tags: z.array(z.string()), year: z.number() diff --git a/content/projects/whats-the-point.yml b/content/projects/whats-the-point.yml index b17742d..108de7c 100644 --- a/content/projects/whats-the-point.yml +++ b/content/projects/whats-the-point.yml @@ -7,6 +7,7 @@ description: icon: i-lucide-spade image: /projects/whats-the-point-light.png imageDark: /projects/whats-the-point-dark.png +imageAnchor: left url: "https://wtp.godden.dev/" tags: ["UI Design", "Front-End Dev"] year: 2025