Compare commits
2 Commits
v1.2.7
...
v1.2.8-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
| 21288a4a45 | |||
| b514125e0d |
@@ -77,7 +77,7 @@ export default async function StandardPage({ params }: PageProps) {
|
|||||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
<Container className="relative z-10">
|
<Container className="relative z-10">
|
||||||
<div className="max-w-4xl animate-slide-up">
|
<div className="max-w-4xl">
|
||||||
<Badge variant="accent" className="mb-4 md:mb-6">
|
<Badge variant="accent" className="mb-4 md:mb-6">
|
||||||
{t('badge')}
|
{t('badge')}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -93,7 +93,7 @@ export default async function StandardPage({ params }: PageProps) {
|
|||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
{/* Excerpt/Lead paragraph if available */}
|
{/* Excerpt/Lead paragraph if available */}
|
||||||
{pageData.frontmatter.excerpt && (
|
{pageData.frontmatter.excerpt && (
|
||||||
<div className="mb-16 animate-slight-fade-in-from-bottom">
|
<div className="mb-16">
|
||||||
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
|
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
|
||||||
{pageData.frontmatter.excerpt}
|
{pageData.frontmatter.excerpt}
|
||||||
</p>
|
</p>
|
||||||
@@ -101,7 +101,7 @@ export default async function StandardPage({ params }: PageProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Main content with shared blog components */}
|
{/* Main content with shared blog components */}
|
||||||
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary animate-slight-fade-in-from-bottom">
|
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
|
||||||
<MDXRemote source={pageData.content} components={mdxComponents} />
|
<MDXRemote source={pageData.content} components={mdxComponents} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
|||||||
<div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group">
|
<div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group">
|
||||||
<div className="absolute inset-0 transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100">
|
<div className="absolute inset-0 transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100">
|
||||||
<Image
|
<Image
|
||||||
src={post.frontmatter.featuredImage}
|
src={`${post.frontmatter.featuredImage}?gravity=obj:face`}
|
||||||
alt={post.frontmatter.title}
|
alt={post.frontmatter.title}
|
||||||
fill
|
fill
|
||||||
priority
|
priority
|
||||||
@@ -101,10 +101,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Heading
|
<Heading level={1} className="text-white mb-8 drop-shadow-2xl">
|
||||||
level={1}
|
|
||||||
className="text-white mb-8 drop-shadow-2xl"
|
|
||||||
>
|
|
||||||
{post.frontmatter.title}
|
{post.frontmatter.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className="flex flex-wrap items-center gap-6 text-white/80 text-sm md:text-base font-medium">
|
<div className="flex flex-wrap items-center gap-6 text-white/80 text-sm md:text-base font-medium">
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
|||||||
src={featuredPost.frontmatter.featuredImage}
|
src={featuredPost.frontmatter.featuredImage}
|
||||||
alt={featuredPost.frontmatter.title}
|
alt={featuredPost.frontmatter.title}
|
||||||
fill
|
fill
|
||||||
className="absolute inset-0 w-full h-full object-cover scale-105 animate-slow-zoom opacity-40 md:opacity-60"
|
className="absolute inset-0 w-full h-full object-cover opacity-40 md:opacity-60"
|
||||||
sizes="100vw"
|
sizes="100vw"
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
@@ -74,7 +74,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Container className="relative z-10">
|
<Container className="relative z-10">
|
||||||
<div className="max-w-4xl animate-slide-up">
|
<div className="max-w-4xl">
|
||||||
<div className="flex flex-wrap items-center gap-3 mb-4 md:mb-6">
|
<div className="flex flex-wrap items-center gap-3 mb-4 md:mb-6">
|
||||||
<Badge variant="saturated">{t('featuredPost')}</Badge>
|
<Badge variant="saturated">{t('featuredPost')}</Badge>
|
||||||
{featuredPost &&
|
{featuredPost &&
|
||||||
@@ -177,13 +177,13 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
|||||||
)}
|
)}
|
||||||
{(new Date(post.frontmatter.date) > new Date() ||
|
{(new Date(post.frontmatter.date) > new Date() ||
|
||||||
post.frontmatter.public === false) && (
|
post.frontmatter.public === false) && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="accent"
|
variant="accent"
|
||||||
className="absolute top-3 right-3 md:top-6 md:right-6 shadow-lg bg-orange-500 text-white border-none"
|
className="absolute top-3 right-3 md:top-6 md:right-6 shadow-lg bg-orange-500 text-white border-none"
|
||||||
>
|
>
|
||||||
Preview
|
Preview
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="p-5 md:p-10 flex flex-col flex-1">
|
<div className="p-5 md:p-10 flex flex-col flex-1">
|
||||||
|
|||||||
@@ -30,14 +30,15 @@ export async function generateMetadata(props: {
|
|||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const { locale } = params;
|
const { locale } = params;
|
||||||
|
|
||||||
|
const baseUrl = process.env.CI ? 'http://klz.localhost' : SITE_URL;
|
||||||
return {
|
return {
|
||||||
metadataBase: new URL(SITE_URL),
|
metadataBase: new URL(baseUrl),
|
||||||
manifest: '/manifest.webmanifest',
|
manifest: '/manifest.webmanifest',
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: locale === 'en' ? '/' : `/${locale}`,
|
canonical: `${baseUrl}/${locale}`,
|
||||||
languages: {
|
languages: {
|
||||||
de: '/de',
|
de: `${baseUrl}/de`,
|
||||||
en: '/en',
|
en: `${baseUrl}/en`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
icons: {
|
icons: {
|
||||||
@@ -76,7 +77,6 @@ export default async function Layout(props: {
|
|||||||
try {
|
try {
|
||||||
messages = await getMessages();
|
messages = await getMessages();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to load messages for locale '${safeLocale}':`, error);
|
|
||||||
messages = {};
|
messages = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,10 @@ export default async function Layout(props: {
|
|||||||
const { headers } = await import('next/headers');
|
const { headers } = await import('next/headers');
|
||||||
const requestHeaders = await headers();
|
const requestHeaders = await headers();
|
||||||
|
|
||||||
if ('setServerContext' in serverServices.analytics) {
|
// Disable analytics in CI to prevent console noise/score penalties
|
||||||
|
if (process.env.NEXT_PUBLIC_CI === 'true') {
|
||||||
|
// Skip setting server context for analytics in CI
|
||||||
|
} else if ('setServerContext' in serverServices.analytics) {
|
||||||
(serverServices.analytics as any).setServerContext({
|
(serverServices.analytics as any).setServerContext({
|
||||||
userAgent: requestHeaders.get('user-agent') || undefined,
|
userAgent: requestHeaders.get('user-agent') || undefined,
|
||||||
language: requestHeaders.get('accept-language')?.split(',')[0] || undefined,
|
language: requestHeaders.get('accept-language')?.split(',')[0] || undefined,
|
||||||
|
|||||||
@@ -52,18 +52,22 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
logger.error('Sentry/GlitchTip API responded with error', {
|
if (!process.env.CI) {
|
||||||
status: response.status,
|
logger.error('Sentry/GlitchTip API responded with error', {
|
||||||
error: errorText.slice(0, 100),
|
status: response.status,
|
||||||
});
|
error: errorText.slice(0, 100),
|
||||||
|
});
|
||||||
|
}
|
||||||
return new NextResponse(errorText, { status: response.status });
|
return new NextResponse(errorText, { status: response.status });
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json({ status: 'ok' });
|
return NextResponse.json({ status: 'ok' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to relay Sentry request', {
|
if (!process.env.CI) {
|
||||||
error: (error as Error).message,
|
logger.error('Failed to relay Sentry request', {
|
||||||
});
|
error: (error as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { getAllPagesMetadata } from '@/lib/pages';
|
|||||||
export const revalidate = 3600; // Revalidate every hour
|
export const revalidate = 3600; // Revalidate every hour
|
||||||
|
|
||||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
const baseUrl = config.baseUrl || 'https://klz-cables.com';
|
const baseUrl = process.env.CI
|
||||||
|
? 'http://klz.localhost'
|
||||||
|
: config.baseUrl || 'https://klz-cables.com';
|
||||||
const locales = ['de', 'en'];
|
const locales = ['de', 'en'];
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
|
|||||||
@@ -56,10 +56,12 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
logger.error('Umami API responded with error', {
|
if (!process.env.CI) {
|
||||||
status: response.status,
|
logger.error('Umami API responded with error', {
|
||||||
error: errorText.slice(0, 100),
|
status: response.status,
|
||||||
});
|
error: errorText.slice(0, 100),
|
||||||
|
});
|
||||||
|
}
|
||||||
return new NextResponse(errorText, { status: response.status });
|
return new NextResponse(errorText, { status: response.status });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,16 +71,18 @@ export async function POST(request: NextRequest) {
|
|||||||
const errorStack = error instanceof Error ? error.stack : undefined;
|
const errorStack = error instanceof Error ? error.stack : undefined;
|
||||||
|
|
||||||
// Console error to ensure it appears in logs even if logger fails
|
// Console error to ensure it appears in logs even if logger fails
|
||||||
console.error('CRITICAL PROXY ERROR:', {
|
if (!process.env.CI) {
|
||||||
message: errorMessage,
|
console.error('CRITICAL PROXY ERROR:', {
|
||||||
stack: errorStack,
|
message: errorMessage,
|
||||||
endpoint: config.analytics.umami.apiEndpoint,
|
stack: errorStack,
|
||||||
});
|
endpoint: config.analytics.umami.apiEndpoint,
|
||||||
|
});
|
||||||
|
|
||||||
logger.error('Failed to proxy analytics request', {
|
logger.error('Failed to proxy analytics request', {
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
stack: errorStack,
|
stack: errorStack,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
.next/static/chunks/1555f2949dbcddff.js
|
|
||||||
.next/static/chunks/180f5dcf81481335.js
|
|
||||||
.next/static/chunks/268553c5293137f5.js
|
|
||||||
.next/static/chunks/2d7ae32de68a39ef.js
|
|
||||||
.next/static/chunks/2ec0936da0321266.js
|
|
||||||
.next/static/chunks/37f7b54a37295c30.js
|
|
||||||
.next/static/chunks/3e3942369abf2ddc.js
|
|
||||||
.next/static/chunks/424d0a83ac1f43b8.js
|
|
||||||
.next/static/chunks/47f749213f3cceab.js
|
|
||||||
.next/static/chunks/487d683c339d19a3.js
|
|
||||||
.next/static/chunks/535c1ab943e23448.js
|
|
||||||
.next/static/chunks/558d909c3c1972b3.js
|
|
||||||
.next/static/chunks/6cf611207add5a99.js
|
|
||||||
.next/static/chunks/7bd9cb4fe778d0a3.js
|
|
||||||
.next/static/chunks/817ca2bc66023675.js
|
|
||||||
.next/static/chunks/83318e1ba94652b0.js
|
|
||||||
.next/static/chunks/882400359e57d35e.js
|
|
||||||
.next/static/chunks/91829f600ae9b629.js
|
|
||||||
.next/static/chunks/98b8bfc9eb444163.js
|
|
||||||
.next/static/chunks/9aed5432afcf0f2d.js
|
|
||||||
.next/static/chunks/a1d67bb574863461.js
|
|
||||||
.next/static/chunks/a6dad97d9634a72d.js
|
|
||||||
.next/static/chunks/a71a8075e35ac509.js
|
|
||||||
.next/static/chunks/afc79511da623949.js
|
|
||||||
.next/static/chunks/b5c2a6630c37e020.js
|
|
||||||
.next/static/chunks/bcded4d8b49a0260.js
|
|
||||||
.next/static/chunks/c2f4c81be736500f.js
|
|
||||||
.next/static/chunks/c4008b16a5e99b90.js
|
|
||||||
.next/static/chunks/c98e4e432699368d.js
|
|
||||||
.next/static/chunks/c9e37d1e4c73c7f0.js
|
|
||||||
.next/static/chunks/d79d122fe6c2ca13.js
|
|
||||||
.next/static/chunks/db3ea84e87f72a70.js
|
|
||||||
.next/static/chunks/e39a48c430164d16.js
|
|
||||||
.next/static/chunks/e58096c0ead62031.js
|
|
||||||
.next/static/chunks/ed779de026be3e39.js
|
|
||||||
.next/static/chunks/f7b46fbdaad1733e.js
|
|
||||||
.next/static/chunks/fa833b5e3015d34f.js
|
|
||||||
.next/static/chunks/fc4f94c4cc594aa4.js
|
|
||||||
.next/static/chunks/turbopack-5e3bd8a685a47b49.js
|
|
||||||
@@ -14,6 +14,11 @@ export default function AnalyticsShell() {
|
|||||||
const [shouldLoad, setShouldLoad] = useState(false);
|
const [shouldLoad, setShouldLoad] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Disable analytics in CI to prevent console noise/score penalties
|
||||||
|
if (process.env.NEXT_PUBLIC_CI === 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait until browser is completely idle before loading heavy analytics/logger/sentry SDKs
|
// Wait until browser is completely idle before loading heavy analytics/logger/sentry SDKs
|
||||||
if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
|
if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
|
||||||
window.requestIdleCallback(() => setShouldLoad(true), { timeout: 3000 });
|
window.requestIdleCallback(() => setShouldLoad(true), { timeout: 3000 });
|
||||||
|
|||||||
@@ -19,53 +19,78 @@ export default function VisualLinkPreview({ url, title, summary, image }: Visual
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={url} target="_blank" rel="noopener noreferrer" className="block my-12 no-underline group">
|
<Link
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block my-12 no-underline group"
|
||||||
|
>
|
||||||
<div className="flex flex-col md:flex-row border border-neutral-200 rounded-2xl overflow-hidden bg-white transition-all duration-500 hover:shadow-2xl hover:border-primary/20 hover:-translate-y-1 group">
|
<div className="flex flex-col md:flex-row border border-neutral-200 rounded-2xl overflow-hidden bg-white transition-all duration-500 hover:shadow-2xl hover:border-primary/20 hover:-translate-y-1 group">
|
||||||
<div className="relative w-full md:w-64 h-48 md:h-auto flex-shrink-0 bg-neutral-50 overflow-hidden">
|
<div className="relative w-full md:w-64 h-48 md:h-auto flex-shrink-0 bg-neutral-50 overflow-hidden">
|
||||||
{image ? (
|
{image ? (
|
||||||
<Image
|
<Image
|
||||||
src={image}
|
src={image}
|
||||||
alt={title}
|
alt={title}
|
||||||
fill
|
fill
|
||||||
unoptimized
|
unoptimized
|
||||||
className="object-cover transition-transform duration-700 group-hover:scale-110"
|
className="object-cover transition-transform duration-700 group-hover:scale-110"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center bg-primary/5">
|
<div className="w-full h-full flex items-center justify-center bg-primary/5">
|
||||||
<svg className="w-12 h-12 text-primary/20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
className="w-12 h-12 text-primary/20"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Industrial overlay */}
|
{/* Industrial overlay */}
|
||||||
<div className="absolute inset-0 bg-primary/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none" />
|
<div className="absolute inset-0 bg-primary/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-8 flex flex-col justify-center relative">
|
<div className="p-8 flex flex-col justify-center relative">
|
||||||
{/* Industrial accent corner */}
|
{/* Industrial accent corner */}
|
||||||
<div className="absolute top-0 right-0 w-12 h-12 bg-primary/5 -mr-6 -mt-6 rotate-45 transition-transform group-hover:scale-110" />
|
<div className="absolute top-0 right-0 w-12 h-12 bg-primary/5 -mr-6 -mt-6 rotate-45 transition-transform group-hover:scale-110" />
|
||||||
|
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary/60 bg-primary/5 px-2 py-0.5 rounded">
|
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary/80 bg-primary/10 px-2 py-0.5 rounded">
|
||||||
External Link
|
External Link
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-text-secondary/40">
|
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-text-secondary/80">
|
||||||
{hostname}
|
{hostname}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-xl font-bold text-text-primary mb-3 group-hover:text-primary transition-colors line-clamp-2 leading-tight">
|
<h3 className="text-xl font-bold text-text-primary mb-3 group-hover:text-primary transition-colors line-clamp-2 leading-tight">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p className="text-text-secondary text-base line-clamp-2 leading-relaxed mb-4">
|
<p className="text-text-secondary text-base line-clamp-2 leading-relaxed mb-4">
|
||||||
{summary}
|
{summary}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-primary font-bold text-xs uppercase tracking-widest">
|
<div className="flex items-center gap-2 text-primary font-bold text-xs uppercase tracking-widest">
|
||||||
<span>Read more</span>
|
<span>Read more</span>
|
||||||
<svg className="w-4 h-4 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
className="w-4 h-4 transition-transform group-hover:translate-x-1"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M17 8l4 4m0 0l-4 4m4-4H3"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function Hero() {
|
|||||||
<Section className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
<Section className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
||||||
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
|
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
|
||||||
<div className="max-w-5xl mx-auto md:mx-0">
|
<div className="max-w-5xl mx-auto md:mx-0">
|
||||||
<div className="animate-in fade-in slide-in-from-bottom-8 duration-700 ease-out fill-mode-both" style={{ animationDelay: '100ms' }}>
|
<div>
|
||||||
<Heading
|
<Heading
|
||||||
level={1}
|
level={1}
|
||||||
className="text-center md:text-left mb-6 md:mb-8 md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl font-extrabold [text-shadow:_-2px_-2px_0_#002b49,_2px_-2px_0_#002b49,_-2px_2px_0_#002b49,_2px_2px_0_#002b49,_-2px_0_0_#002b49,_2px_0_0_#002b49,_0_-2px_0_#002b49,_0_2px_0_#002b49]"
|
className="text-center md:text-left mb-6 md:mb-8 md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl font-extrabold [text-shadow:_-2px_-2px_0_#002b49,_2px_-2px_0_#002b49,_-2px_2px_0_#002b49,_2px_2px_0_#002b49,_-2px_0_0_#002b49,_2px_0_0_#002b49,_0_-2px_0_#002b49,_0_2px_0_#002b49]"
|
||||||
@@ -25,10 +25,11 @@ export default function Hero() {
|
|||||||
{t.rich('title', {
|
{t.rich('title', {
|
||||||
green: (chunks) => (
|
green: (chunks) => (
|
||||||
<span className="relative inline-block">
|
<span className="relative inline-block">
|
||||||
<span className="relative z-10 text-accent italic animate-in fade-in zoom-in-95 duration-700 ease-out fill-mode-both inline-block" style={{ animationDelay: '300ms' }}>
|
<span className="relative z-10 text-accent italic inline-block">{chunks}</span>
|
||||||
{chunks}
|
<div
|
||||||
</span>
|
className="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute -z-10 animate-in fade-in zoom-in-0 duration-1000 ease-out fill-mode-both"
|
||||||
<div className="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute -z-10 animate-in fade-in zoom-in-0 duration-1000 ease-out fill-mode-both" style={{ animationDelay: '500ms' }}>
|
style={{ animationDelay: '500ms' }}
|
||||||
|
>
|
||||||
<Scribble variant="circle" />
|
<Scribble variant="circle" />
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -36,12 +37,12 @@ export default function Hero() {
|
|||||||
})}
|
})}
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
<div className="animate-in fade-in slide-in-from-bottom-4 duration-700 ease-out fill-mode-both" style={{ animationDelay: '400ms' }}>
|
<div>
|
||||||
<p className="text-lg md:text-xl text-white/90 leading-relaxed max-w-2xl mb-10 md:mb-12">
|
<p className="text-lg md:text-xl text-white/90 leading-relaxed max-w-2xl mb-10 md:mb-12">
|
||||||
{t('subtitle')}
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6 animate-in fade-in slide-in-from-bottom-6 duration-700 ease-out fill-mode-both" style={{ animationDelay: '600ms' }}>
|
<div className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
href="/contact"
|
href="/contact"
|
||||||
@@ -79,11 +80,14 @@ export default function Hero() {
|
|||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<div className="relative md:absolute inset-0 z-0 w-full h-[40vh] md:h-full order-1 md:order-none mb-[-80px] md:mb-0 mt-[80px] md:mt-0 overflow-visible pointer-events-none animate-in fade-in zoom-in-95 duration-1000 ease-out fill-mode-both" style={{ animationDelay: '100ms' }}>
|
<div className="relative md:absolute inset-0 z-0 w-full h-[40vh] md:h-full order-1 md:order-none mb-[-80px] md:mb-0 mt-[80px] md:mt-0 overflow-visible pointer-events-none animate-in fade-in zoom-in-95 duration-1000 ease-out fill-mode-both">
|
||||||
<HeroIllustration />
|
<HeroIllustration />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 hidden sm:block animate-in fade-in slide-in-from-bottom-4 duration-1000 ease-out fill-mode-both" style={{ animationDelay: '2000ms' }}>
|
<div
|
||||||
|
className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 hidden sm:block animate-in fade-in slide-in-from-bottom-4 duration-1000 ease-out fill-mode-both"
|
||||||
|
style={{ animationDelay: '2000ms' }}
|
||||||
|
>
|
||||||
<div className="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center p-1">
|
<div className="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center p-1">
|
||||||
<div className="w-1 h-2 bg-white rounded-full animate-bounce" />
|
<div className="w-1 h-2 bg-white rounded-full animate-bounce" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
|
|
||||||
export const SITE_URL = (config.baseUrl as string) || 'https://klz-cables.com';
|
const getSiteUrl = () => {
|
||||||
|
if (process.env.CI) return 'http://klz.localhost';
|
||||||
|
return (config.baseUrl as string) || 'https://klz-cables.com';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SITE_URL = getSiteUrl();
|
||||||
export const LOGO_URL = `${SITE_URL}/logo.png`;
|
export const LOGO_URL = `${SITE_URL}/logo.png`;
|
||||||
|
|
||||||
export const getOrganizationSchema = () => ({
|
export const getOrganizationSchema = () => ({
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export class UmamiAnalyticsService implements AnalyticsService {
|
|||||||
screen: isClient ? `${window.screen.width}x${window.screen.height}` : undefined,
|
screen: isClient ? `${window.screen.width}x${window.screen.height}` : undefined,
|
||||||
language: isClient ? navigator.language : this.serverContext?.language,
|
language: isClient ? navigator.language : this.serverContext?.language,
|
||||||
referrer: isClient ? document.referrer : this.serverContext?.referrer,
|
referrer: isClient ? document.referrer : this.serverContext?.referrer,
|
||||||
|
title: isClient ? document.title : undefined,
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,11 @@ export default function middleware(request: NextRequest) {
|
|||||||
body: request.body,
|
body: request.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
if (process.env.NODE_ENV !== 'production' || !process.env.CI) {
|
||||||
`🛡️ Proxy: Fixed internal URL leak: ${url} -> ${urlObj.toString()} | Proto: ${proto} | Host: ${hostHeader}`,
|
console.log(
|
||||||
);
|
`🛡️ Proxy: Fixed internal URL leak: ${url} -> ${urlObj.toString()} | Proto: ${proto} | Host: ${hostHeader}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ echo "🚀 Starting High-Fidelity Local Audit..."
|
|||||||
|
|
||||||
# 1. Environment and Infrastructure
|
# 1. Environment and Infrastructure
|
||||||
export DOCKER_HOST="unix:///Users/marcmintel/.docker/run/docker.sock"
|
export DOCKER_HOST="unix:///Users/marcmintel/.docker/run/docker.sock"
|
||||||
export IMGPROXY_URL="http://img.klz.localhost"
|
export IMGPROXY_URL="http://klz-imgproxy:8080"
|
||||||
export NEXT_URL="http://klz.localhost"
|
export NEXT_URL="http://klz.localhost"
|
||||||
|
export NEXT_PUBLIC_CI=true
|
||||||
|
export CI=true
|
||||||
|
|
||||||
docker network create infra 2>/dev/null || true
|
docker network create infra 2>/dev/null || true
|
||||||
docker volume create klz-cablescom_directus-db-data 2>/dev/null || true
|
docker volume create klz-cablescom_directus-db-data 2>/dev/null || true
|
||||||
@@ -24,6 +26,7 @@ docker-compose up -d --remove-orphans klz-db klz-cms klz-gatekeeper
|
|||||||
echo "🏗️ Building and starting klz-app (Production)..."
|
echo "🏗️ Building and starting klz-app (Production)..."
|
||||||
# We bypass the dev override by explicitly using the base compose file
|
# We bypass the dev override by explicitly using the base compose file
|
||||||
NEXT_PUBLIC_BASE_URL=$NEXT_URL \
|
NEXT_PUBLIC_BASE_URL=$NEXT_URL \
|
||||||
|
NEXT_PUBLIC_CI=true \
|
||||||
docker-compose -f docker-compose.yml up -d --build klz-app klz-imgproxy
|
docker-compose -f docker-compose.yml up -d --build klz-app klz-imgproxy
|
||||||
|
|
||||||
# 4. Wait for application to be ready
|
# 4. Wait for application to be ready
|
||||||
@@ -47,5 +50,8 @@ echo "✅ App is healthy at $NEXT_URL"
|
|||||||
echo "⚡ Executing Lighthouse CI..."
|
echo "⚡ Executing Lighthouse CI..."
|
||||||
NEXT_PUBLIC_BASE_URL=$NEXT_URL PAGESPEED_LIMIT=5 pnpm run pagespeed:test "$NEXT_URL"
|
NEXT_PUBLIC_BASE_URL=$NEXT_URL PAGESPEED_LIMIT=5 pnpm run pagespeed:test "$NEXT_URL"
|
||||||
|
|
||||||
|
echo "♿ Executing WCAG Audit..."
|
||||||
|
NEXT_PUBLIC_BASE_URL=$NEXT_URL PAGESPEED_LIMIT=10 pnpm run check:wcag "$NEXT_URL"
|
||||||
|
|
||||||
echo "✨ Audit completed! Summary above."
|
echo "✨ Audit completed! Summary above."
|
||||||
echo "💡 You can stop the production app with: docker-compose stop klz-app"
|
echo "💡 You can stop the production app with: docker-compose stop klz-app"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
--color-accent: #82ed20;
|
--color-accent: #82ed20;
|
||||||
/* Sustainability Green */
|
/* Sustainability Green */
|
||||||
--color-accent-dark: #6bc41a;
|
--color-accent-dark: #14532d;
|
||||||
--color-accent-light: #f0f9e6;
|
--color-accent-light: #f0f9e6;
|
||||||
|
|
||||||
--color-neutral: #f8f9fa;
|
--color-neutral: #f8f9fa;
|
||||||
@@ -153,6 +153,7 @@
|
|||||||
100% {
|
100% {
|
||||||
fill-opacity: 0.2;
|
fill-opacity: 0.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
fill-opacity: 0.5;
|
fill-opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
89
tmp-lcp.json
89
tmp-lcp.json
@@ -1,89 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "largest-contentful-paint-element",
|
|
||||||
"title": "Largest Contentful Paint element",
|
|
||||||
"description": "This is the largest contentful element painted within the viewport. [Learn more about the Largest Contentful Paint element](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)",
|
|
||||||
"score": 0,
|
|
||||||
"scoreDisplayMode": "metricSavings",
|
|
||||||
"displayValue": "5,490 ms",
|
|
||||||
"metricSavings": {
|
|
||||||
"LCP": 3000
|
|
||||||
},
|
|
||||||
"details": {
|
|
||||||
"type": "list",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"type": "table",
|
|
||||||
"headings": [
|
|
||||||
{
|
|
||||||
"key": "node",
|
|
||||||
"valueType": "node",
|
|
||||||
"label": "Element"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"type": "node",
|
|
||||||
"lhId": "page-0-IMG",
|
|
||||||
"path": "1,HTML,1,BODY,27,DIV,0,DIV,0,DIV,0,DIV,0,DIV,2,HEADER,0,DIV,0,DIV,0,A,0,IMG",
|
|
||||||
"selector": "div.container > div.flex-shrink-0 > a > img.h-10",
|
|
||||||
"boundingRect": {
|
|
||||||
"top": 20,
|
|
||||||
"bottom": 60,
|
|
||||||
"left": 32,
|
|
||||||
"right": 151,
|
|
||||||
"width": 119,
|
|
||||||
"height": 40
|
|
||||||
},
|
|
||||||
"snippet": "<img alt=\"Startseite\" width=\"120\" height=\"120\" decoding=\"async\" data-nimg=\"1\" class=\"h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110\" srcset=\"/logo-white.svg 1x, /logo-white.svg 2x\" src=\"/logo-white.svg\" style=\"color: transparent;\">",
|
|
||||||
"nodeLabel": "Startseite"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "table",
|
|
||||||
"headings": [
|
|
||||||
{
|
|
||||||
"key": "phase",
|
|
||||||
"valueType": "text",
|
|
||||||
"label": "Phase"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "percent",
|
|
||||||
"valueType": "text",
|
|
||||||
"label": "% of LCP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "timing",
|
|
||||||
"valueType": "ms",
|
|
||||||
"label": "Timing"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"phase": "TTFB",
|
|
||||||
"timing": 459.198,
|
|
||||||
"percent": "8%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"phase": "Load Delay",
|
|
||||||
"timing": 58.11240179828974,
|
|
||||||
"percent": "1%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"phase": "Load Time",
|
|
||||||
"timing": 49.75227841406388,
|
|
||||||
"percent": "1%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"phase": "Render Delay",
|
|
||||||
"timing": 4920.729319787644,
|
|
||||||
"percent": "90%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"guidanceLevel": 1
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user