Compare commits
8 Commits
fix/qa-wor
...
v2.2.30
| Author | SHA1 | Date | |
|---|---|---|---|
| 000bb89670 | |||
| 64563dfcd8 | |||
| e784b90de1 | |||
| c908fcb4b8 | |||
| 6cb390024b | |||
| 089029cb85 | |||
| b3c6197336 | |||
| b60e7c2aea |
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2895
.lighthouseci/lhr-1782064787195.html
Normal file
2895
.lighthouseci/lhr-1782064787195.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
12
Dockerfile
12
Dockerfile
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,6 +42,10 @@ 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(() => {
|
||||
let interactionTriggered = false;
|
||||
@@ -50,19 +55,17 @@ export function HeroVideo(props: HeroVideoProps) {
|
||||
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 })
|
||||
);
|
||||
|
||||
return () => {
|
||||
['scroll', 'mousemove', 'touchstart', 'keydown'].forEach((event) =>
|
||||
['scroll', 'mousemove', 'touchstart', 'keydown', 'click'].forEach((event) =>
|
||||
window.removeEventListener(event, handleInteraction)
|
||||
);
|
||||
};
|
||||
@@ -73,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"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.2.29",
|
||||
"version": "2.2.30",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
BIN
public/assets/photos/DJI_0048.JPG
Executable file → Normal file
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
1482
public/fonts/Inter-Bold.ttf
Normal file
File diff suppressed because one or more lines are too long
BIN
public/fonts/Inter-Bold.woff
Normal file
BIN
public/fonts/Inter-Bold.woff
Normal file
Binary file not shown.
1482
public/fonts/Inter-Regular.ttf
Normal file
1482
public/fonts/Inter-Regular.ttf
Normal file
File diff suppressed because one or more lines are too long
BIN
public/fonts/Inter-Regular.woff
Normal file
BIN
public/fonts/Inter-Regular.woff
Normal file
Binary file not shown.
1
test_og.png
Normal file
1
test_og.png
Normal file
@@ -0,0 +1 @@
|
||||
Bad Gateway
|
||||
BIN
test_og_local.png
Normal file
BIN
test_og_local.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Reference in New Issue
Block a user