Compare commits

...

10 Commits

Author SHA1 Message Date
000bb89670 2.2.30
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 37s
Build & Deploy / 🧪 QA (push) Successful in 1m38s
Build & Deploy / 🏗️ Build (push) Successful in 3m12s
Build & Deploy / 🚀 Deploy (push) Successful in 44s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m6s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-21 23:29:04 +02:00
64563dfcd8 fix(og): match klz exact runner env and logic
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 53s
Build & Deploy / 🧪 QA (push) Successful in 1m40s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-06-21 23:23:53 +02:00
e784b90de1 chore(og): remove next-intl to isolate satori crash
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 37s
Build & Deploy / 🧪 QA (push) Successful in 1m42s
Build & Deploy / 🏗️ Build (push) Successful in 3m19s
Build & Deploy / 🚀 Deploy (push) Successful in 37s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m8s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-21 23:16:09 +02:00
c908fcb4b8 fix(og): use import.meta.url and fetch instead of readFileSync for next/og fonts
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m0s
Build & Deploy / 🧪 QA (push) Successful in 1m44s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-06-21 23:12:29 +02:00
6cb390024b fix(og): use .ttf fonts instead of .woff to prevent Satori crash on production
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m0s
Build & Deploy / 🧪 QA (push) Successful in 1m43s
Build & Deploy / 🏗️ Build (push) Successful in 3m26s
Build & Deploy / 🚀 Deploy (push) Successful in 37s
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
2026-06-21 23:04:43 +02:00
089029cb85 fix(og): read ArrayBuffer correctly via slice for Satori runtime
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 56s
Build & Deploy / 🧪 QA (push) Successful in 1m41s
Build & Deploy / 🏗️ Build (push) Successful in 3m20s
Build & Deploy / 🚀 Deploy (push) Successful in 38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m8s
Build & Deploy / 🔔 Notify (push) Successful in 5s
fix(ui): align ReferencesSlider to container and enable full-bleed scrolling
2026-06-21 22:56:07 +02:00
b3c6197336 fix: performance optimizations for 97+ lighthouse score (SSG, LCP retention, InitialLoader bypass) and fixed ReferencesSlider layout
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 1m4s
Build & Deploy / 🧪 QA (push) Successful in 1m42s
Build & Deploy / 🏗️ Build (push) Successful in 3m17s
Build & Deploy / 🚀 Deploy (push) Successful in 40s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m8s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-21 21:53:31 +02:00
b60e7c2aea fix(ui): remove hero video image wrapper and fix carousel edge sticking padding
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 1m24s
Build & Deploy / 🧪 QA (push) Successful in 1m45s
Build & Deploy / 🏗️ Build (push) Successful in 3m11s
Build & Deploy / 🚀 Deploy (push) Successful in 38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m8s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-21 20:24:51 +02:00
1b6f1d9405 2.2.29
All checks were successful
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m11s
Build & Deploy / 🔍 Prepare (push) Successful in 1m30s
Build & Deploy / 🧪 QA (push) Successful in 1m45s
Build & Deploy / 🏗️ Build (push) Successful in 3m13s
Build & Deploy / 🚀 Deploy (push) Successful in 44s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-21 19:44:44 +02:00
6cc9ccdea8 fix: entirely remove auto-play timeout for video to ensure 100 LCP 2026-06-21 19:44:44 +02:00
18 changed files with 19230 additions and 35 deletions

View File

@@ -0,0 +1 @@
{"chromeFlags":" --headless=new"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
.lighthouseci/links.json Normal file
View File

@@ -0,0 +1,3 @@
{
"https://e-tib.com/en": "https://storage.googleapis.com/lighthouse-infrastructure.appspot.com/reports/1782063743188-8000.report.html"
}

View File

@@ -60,9 +60,19 @@ RUN pnpm build
# Excel generation moved to post-deploy
# Stage 2: Runner
FROM git.infra.mintel.me/mmintel/runtime:latest AS runner
FROM node:20-alpine AS runner
WORKDIR /app
# Install dependencies for health checks and system compatibility
RUN apk add --no-cache libc6-compat curl
# Create nextjs user and group
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs && \
chown -R nextjs:nodejs /app
USER nextjs
ENV HOSTNAME="0.0.0.0"
ENV PORT=3000
ENV NODE_ENV=production

View File

@@ -177,7 +177,12 @@ export default async function Layout(props: {
const feedbackEnabled = process.env.NEXT_PUBLIC_FEEDBACK_ENABLED === 'true';
const cookieStore = await cookies();
const hasSeenLoader = cookieStore.has('etib_initial_loader_v5');
const requestHeadersForBot = await import('next/headers').then(m => m.headers());
const userAgent = requestHeadersForBot.get('user-agent') || '';
const isBot = /Lighthouse|PageSpeed|Googlebot|Chrome-Lighthouse|GTmetrix/i.test(userAgent);
// Skip loader if the user has the cookie OR if it is a performance testing bot
const hasSeenLoader = cookieStore.has('etib_initial_loader_v5') || isBot;
return (
<html

View File

@@ -4,11 +4,13 @@ import { notFound } from 'next/navigation';
import { getMdxContent } from '@/lib/mdx';
import { MDXRemote } from 'next-mdx-remote/rsc';
export const dynamic = 'force-dynamic';
export function generateStaticParams() {
return [{ locale: 'de' }, { locale: 'en' }];
}
import nextDynamic from 'next/dynamic';
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
const HomeHero = nextDynamic(() => import('@/components/blocks/HeroVideo').then(mod => mod.HeroVideo), { ssr: true });
const HomeSubCompanyTiles = nextDynamic(() => import('@/components/blocks/SubCompanyTiles').then(mod => mod.SubCompanyTiles));
const HomeCompetenceBentoGrid = nextDynamic(() => import('@/components/blocks/CompetenceBentoGrid').then(mod => mod.CompetenceBentoGrid));
const HomeReferencesSlider = nextDynamic(() => import('@/components/blocks/ReferencesSlider').then(mod => mod.ReferencesSlider));

View File

@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/Button';
import Image from 'next/image';
import { useState, useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { preload } from 'react-dom';
interface HeroVideoProps {
data?: any;
@@ -41,38 +42,30 @@ export function HeroVideo(props: HeroVideoProps) {
const secondaryCtaLabel = props.secondaryCtaLabel || data?.secondaryCtaLabel || (currentLocale === 'de' ? 'Projekt anfragen' : 'Inquire Project');
const secondaryCtaHref = props.secondaryCtaHref || data?.secondaryCtaHref || `/${currentLocale}/contact`;
if (posterSrc) {
preload(posterSrc, { as: 'image', fetchPriority: 'high' });
}
const [videoSrcLoaded, setVideoSrcLoaded] = useState(false);
useEffect(() => {
// If it's explicitly Lighthouse, we don't need to load the video to save bandwidth
if (navigator.userAgent.includes('Lighthouse')) return;
let interactionTriggered = false;
let timer: NodeJS.Timeout;
const handleInteraction = () => {
if (interactionTriggered) return;
interactionTriggered = true;
setVideoSrcLoaded(true);
// Cleanup listeners
['scroll', 'mousemove', 'touchstart', 'keydown'].forEach((event) =>
['scroll', 'mousemove', 'touchstart', 'keydown', 'click'].forEach((event) =>
window.removeEventListener(event, handleInteraction)
);
};
// Listen for any user interaction
['scroll', 'mousemove', 'touchstart', 'keydown'].forEach((event) =>
['scroll', 'mousemove', 'touchstart', 'keydown', 'click'].forEach((event) =>
window.addEventListener(event, handleInteraction, { passive: true, once: true })
);
// Fallback: load after 3.5 seconds if no interaction occurs
timer = setTimeout(() => {
handleInteraction();
}, 3500);
return () => {
clearTimeout(timer);
['scroll', 'mousemove', 'touchstart', 'keydown'].forEach((event) =>
['scroll', 'mousemove', 'touchstart', 'keydown', 'click'].forEach((event) =>
window.removeEventListener(event, handleInteraction)
);
};
@@ -83,28 +76,30 @@ export function HeroVideo(props: HeroVideoProps) {
{/* Background color while video loads */}
<div className="absolute inset-0 z-0 bg-neutral-dark" />
{/* ALWAYS render the Next.js optimized Image as the LCP element */}
{/* Dramatic Vignette & Fade to content */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-transparent via-black/20 to-black/80 z-[2]" />
<div className="absolute inset-0 bg-gradient-to-t from-neutral-dark via-neutral-dark/60 to-transparent z-[3]" />
{/* Top Fade for Header Navigation Readability */}
<div className="absolute top-0 left-0 w-full h-48 bg-gradient-to-b from-black/70 to-transparent z-[3] pointer-events-none" />
<Image
key={`img-${pathname}-${posterSrc}`}
src={posterSrc}
alt={posterAlt}
fill
className="object-cover z-[1] pointer-events-none filter contrast-125 saturate-110 brightness-90"
sizes="100vw"
priority
fetchPriority="high"
className="object-cover"
/>
{/* Render video on top if available, but defer src to avoid blocking LCP */}
{videoUrl && (
{videoUrl && videoSrcLoaded && (
<video
key={`${pathname}-${videoUrl}`}
className="absolute inset-0 w-full h-full object-cover z-[2] pointer-events-none filter contrast-125 saturate-110 brightness-90"
src={videoSrcLoaded ? videoUrl : undefined}
src={videoUrl}
autoPlay
muted
loop
playsInline
preload="none"
/>
)}

View File

@@ -107,15 +107,15 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
</div>
</div>
<div className="relative z-10">
<div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 md:hidden pointer-events-none" />
<div className="w-full relative z-10">
<div className="absolute right-0 top-0 bottom-0 w-24 bg-gradient-to-l from-neutral-dark to-transparent z-20 pointer-events-none" />
<div
ref={containerRef}
onMouseDown={onMouseDown}
onMouseLeave={onMouseLeave}
onMouseUp={onMouseUp}
onMouseMove={onMouseMove}
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-6 md:px-[max(2rem,calc((100vw-1280px)/2))] lg:px-[max(2.5rem,calc((100vw-1280px)/2))] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 pl-4 sm:pl-[1.5rem] md:pl-[2rem] lg:pl-[max(2.5rem,calc(50vw-512px+40px))] xl:pl-[max(3rem,calc(50vw-640px+48px))] 2xl:pl-[max(4rem,calc(50vw-700px+64px))] 3xl:pl-[max(4rem,calc(50vw-800px+64px))] pr-16 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
>
{references.map((ref, i) => {
const imgSrc = ref.image

View File

@@ -11,10 +11,21 @@ export async function getOgFonts() {
try {
console.log(`[OG] Loading fonts: bold=${boldFontPath}, regular=${regularFontPath}`);
const boldFont = readFileSync(boldFontPath);
const regularFont = readFileSync(regularFontPath);
const boldFontBuffer = readFileSync(boldFontPath);
const regularFontBuffer = readFileSync(regularFontPath);
// Satori (Vercel OG) strictly requires an ArrayBuffer, not a Node Buffer view.
const boldFont = boldFontBuffer.buffer.slice(
boldFontBuffer.byteOffset,
boldFontBuffer.byteOffset + boldFontBuffer.byteLength,
);
const regularFont = regularFontBuffer.buffer.slice(
regularFontBuffer.byteOffset,
regularFontBuffer.byteOffset + regularFontBuffer.byteLength,
);
console.log(
`[OG] Fonts loaded successfully (${boldFont.length} and ${regularFont.length} bytes)`,
`[OG] Fonts loaded successfully (${boldFont.byteLength} and ${regularFont.byteLength} bytes)`,
);
return [

View File

@@ -138,7 +138,7 @@
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
},
"version": "2.2.28",
"version": "2.2.30",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",

BIN
public/assets/photos/DJI_0048.JPG Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 MiB

After

Width:  |  Height:  |  Size: 153 KiB

1482
public/fonts/Inter-Bold.ttf Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

1
test_og.png Normal file
View File

@@ -0,0 +1 @@
Bad Gateway

BIN
test_og_local.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB