Compare commits

...

2 Commits

Author SHA1 Message Date
d538d7b9ec fix(blog): ensure target environment vars are parsed for accurate strict filtering in prod, and integrate face detection gravity for blog thumbnails
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 12s
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
2026-02-20 18:54:09 +01:00
8c08b552cf fix: svg stroke width 2026-02-20 18:48:10 +01:00
5 changed files with 49 additions and 22 deletions

View File

@@ -5,6 +5,7 @@ import { MDXRemote } from 'next-mdx-remote/rsc';
import { getPostBySlug, getAdjacentPosts, getReadingTime, getHeadings } from '@/lib/blog';
import { Metadata } from 'next';
import Link from 'next/link';
import Image from 'next/image';
import PostNavigation from '@/components/blog/PostNavigation';
import PowerCTA from '@/components/blog/PowerCTA';
import TableOfContents from '@/components/blog/TableOfContents';
@@ -77,10 +78,16 @@ export default async function BlogPost({ params }: BlogPostProps) {
{/* Featured Image Header */}
{post.frontmatter.featuredImage ? (
<div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group">
<div
className="absolute inset-0 bg-cover bg-center transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100"
style={{ backgroundImage: `url(${post.frontmatter.featuredImage})` }}
/>
<div className="absolute inset-0 transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100">
<Image
src={`${post.frontmatter.featuredImage}?gravity=obj:face`}
alt={post.frontmatter.title}
fill
priority
className="object-cover"
sizes="100vw"
/>
</div>
<div className="absolute inset-0 bg-gradient-to-t from-neutral-dark via-neutral-dark/40 to-transparent" />
{/* Title overlay on image */}

View File

@@ -62,7 +62,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
{featuredPost && featuredPost.frontmatter.featuredImage && (
<>
<Image
src={featuredPost.frontmatter.featuredImage}
src={`${featuredPost.frontmatter.featuredImage}?gravity=obj:face`}
alt={featuredPost.frontmatter.title}
fill
className="absolute inset-0 w-full h-full object-cover scale-105 animate-slow-zoom opacity-40 md:opacity-60"
@@ -160,7 +160,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
{post.frontmatter.featuredImage && (
<div className="relative h-48 md:h-72 overflow-hidden">
<Image
src={post.frontmatter.featuredImage}
src={`${post.frontmatter.featuredImage}?gravity=obj:face`}
alt={post.frontmatter.title}
fill
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"

View File

@@ -127,6 +127,7 @@ export default function HeroIllustration() {
const viewBox = isMobile ? '400 0 1000 1100' : '-400 -200 1800 1100';
const scale = isMobile ? 1.44 : 1;
const opacity = isMobile ? 0.6 : 0.85;
const strokeMultiplier = isMobile ? 2.5 : 1;
return (
<div className="absolute inset-0 z-0 overflow-visible bg-primary w-full h-full">
@@ -184,7 +185,7 @@ export default function HeroIllustration() {
x2={end.x}
y2={end.y}
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
/>
);
})}
@@ -199,14 +200,14 @@ export default function HeroIllustration() {
x2={end.x}
y2={end.y}
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
/>
);
})}
</g>
{/* POWER LINES */}
<g stroke="white" strokeWidth="2" strokeOpacity="0.2">
<g stroke="white" strokeWidth={2 * strokeMultiplier} strokeOpacity="0.2">
{POWER_LINES.map((line, i) => {
const from = gridToScreen(line.from.col, line.from.row);
const to = gridToScreen(line.to.col, line.to.row);
@@ -230,7 +231,7 @@ export default function HeroIllustration() {
x2={to.x}
y2={to.y}
stroke="url(#energy-pulse)"
strokeWidth="3"
strokeWidth={3 * strokeMultiplier}
strokeLinecap="round"
style={{
strokeDasharray: `${length * 0.2} ${length * 0.8}`,
@@ -252,7 +253,7 @@ export default function HeroIllustration() {
fill="white"
fillOpacity="0.05"
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
strokeOpacity="0.2"
/>
<path
@@ -260,7 +261,7 @@ export default function HeroIllustration() {
fill="white"
fillOpacity="0.1"
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
strokeOpacity="0.3"
/>
<circle
@@ -285,7 +286,7 @@ export default function HeroIllustration() {
x2="0"
y2="-60"
stroke="white"
strokeWidth="2"
strokeWidth={2 * strokeMultiplier}
strokeOpacity="0.3"
/>
<g transform="translate(0, -60)">
@@ -303,7 +304,7 @@ export default function HeroIllustration() {
x2="0"
y2="-30"
stroke="white"
strokeWidth="1.5"
strokeWidth={1.5 * strokeMultiplier}
strokeOpacity="0.4"
transform={`rotate(${angle})`}
/>
@@ -325,7 +326,7 @@ export default function HeroIllustration() {
fill="white"
fillOpacity="0.05"
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
strokeOpacity="0.2"
/>
<path
@@ -337,7 +338,7 @@ export default function HeroIllustration() {
fill="white"
fillOpacity="0.08"
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
strokeOpacity="0.3"
/>
</g>
@@ -352,7 +353,7 @@ export default function HeroIllustration() {
<path
d="M -6 0 L -3 -45 M 6 0 L 3 -45"
stroke="white"
strokeWidth="1.5"
strokeWidth={1.5 * strokeMultiplier}
strokeOpacity="0.3"
/>
<line
@@ -361,7 +362,7 @@ export default function HeroIllustration() {
x2="12"
y2="-40"
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
strokeOpacity="0.2"
/>
</g>
@@ -380,7 +381,7 @@ export default function HeroIllustration() {
fill="white"
fillOpacity="0.08"
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
strokeOpacity="0.2"
/>
<path
@@ -388,7 +389,7 @@ export default function HeroIllustration() {
fill="white"
fillOpacity="0.05"
stroke="white"
strokeWidth="1"
strokeWidth={1 * strokeMultiplier}
strokeOpacity="0.15"
/>
</g>

View File

@@ -20,6 +20,8 @@ const booleanSchema = z.preprocess((val) => {
const envExtension = {
// Project specific overrides or additions
AUTH_COOKIE_NAME: z.string().default('klz_gatekeeper_session'),
TARGET: z.string().optional(),
NEXT_PUBLIC_TARGET: z.string().optional(),
// Gatekeeper specifics not in base
GATEKEEPER_URL: z.string().url().default('http://gatekeeper:3000'),

View File

@@ -23,11 +23,28 @@ export default function imgproxyLoader({
return src;
}
// Check if src contains custom gravity query parameter
let gravity = 'sm'; // Use smart gravity (content-aware) by default
let cleanSrc = src;
try {
// Dummy base needed for relative URLs
const url = new URL(src, 'http://localhost');
const customGravity = url.searchParams.get('gravity');
if (customGravity) {
gravity = customGravity;
url.searchParams.delete('gravity');
cleanSrc = src.startsWith('http') ? url.href : url.pathname + url.search;
}
} catch (e) {
// Fallback if parsing fails
}
// We use the width provided by Next.js for responsive images
// Height is set to 0 to maintain aspect ratio
return getImgproxyUrl(src, {
return getImgproxyUrl(cleanSrc, {
width,
resizing_type: 'fit',
gravity: 'sm', // Use smart gravity (content-aware) instead of face detection (requires ML)
gravity,
});
}